139 lines
4.3 KiB
Python
Executable File
139 lines
4.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Read only, Amiga LZX[1] archiver Virtual filesystem executive for Midnight
|
|
Commander.
|
|
|
|
Tested against python 3.8, unlzx[1] 1.1 and mc 4.8.22
|
|
|
|
[1] ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme
|
|
|
|
Changelog:
|
|
1.3 Code cleanup
|
|
1.2 Use python3
|
|
1.1 Moved common code into extfslib library
|
|
1.0 Initial release
|
|
|
|
Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
|
|
Date: 2023-10-20
|
|
Version: 1.3
|
|
Licence: BSD
|
|
"""
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
import extfslib
|
|
|
|
|
|
class ULzx(extfslib.Archive):
|
|
"""LZX archive handle. Provides interface to MC's extfs subsystem"""
|
|
LINE_PAT = re.compile(r"^\s+(?P<size>\d+)\s+"
|
|
r"((n/a)|\d+)\s"
|
|
r"(?P<time>\d{2}:\d{2}:\d{2})\s+"
|
|
r"(?P<date>\d+-[a-z]{3}-\d{4})\s"
|
|
r"(?P<perms>[h-][s-][p-][a-][r-][w-][e-][d-])\s"
|
|
r"\"(?P<fpath>.*)\"")
|
|
ARCHIVER = "unlzx"
|
|
CMDS = {"list": "-v",
|
|
"read": "-x"}
|
|
DATETIME = "%02d-%02d-%s %02d:%02d"
|
|
|
|
def _get_date(self, time, date):
|
|
"""Return MM-DD-YYYY hh:mm formatted date out of time and date
|
|
strings"""
|
|
month_list = ["jan", "fe", "mar", "apr", "may", "jun", "jul",
|
|
"aug", "sep", "oct", "nov", "dec"]
|
|
day, month, year = date.split("-")
|
|
month = month_list.index(month) + 1
|
|
hours, minutes, dummy = time.split(":")
|
|
return self.DATETIME % (month, int(day), year, int(hours),
|
|
int(minutes))
|
|
|
|
def _get_dir(self):
|
|
"""Prepare archive file listing"""
|
|
contents = []
|
|
|
|
out = subprocess.run([self.ARCHIVER, self.CMDS['list'], self._arch],
|
|
capture_output=True, encoding="latin-1")
|
|
if out.stderr:
|
|
sys.stderr.write(out.stderr)
|
|
|
|
for line in out.stdout.split("\n"):
|
|
match = self.LINE_PAT.match(line)
|
|
if not match:
|
|
continue
|
|
|
|
match_entry = match.groupdict()
|
|
entry = {}
|
|
for key in match_entry:
|
|
entry[key] = match_entry[key]
|
|
del match_entry
|
|
|
|
entry['datetime'] = self._get_date(entry['time'], entry['date'])
|
|
entry['display_name'] = self._map_name(entry['fpath'])
|
|
entry['perms'] = "-rw-r--r--" # lzx doesn't store empty dirs
|
|
entry['uid'] = str(self._uid)
|
|
entry['gid'] = str(self._gid)
|
|
contents.append(entry)
|
|
|
|
return contents
|
|
|
|
def list(self):
|
|
"""Output contents of the archive to stdout"""
|
|
for entry in self._contents:
|
|
sys.stdout.write(self.ITEM.decode('utf-8') % entry)
|
|
return 0
|
|
|
|
def run(self, dst):
|
|
"""Execute file out of archive"""
|
|
fdesc, tmp_file = tempfile.mkstemp()
|
|
os.close(fdesc)
|
|
result = 0
|
|
|
|
if self.copyout(dst, tmp_file) != 0:
|
|
result = 1
|
|
|
|
os.chmod(tmp_file, int("700", 8))
|
|
|
|
with open(os.devnull, "w") as fnull:
|
|
result = subprocess.run([tmp_file], stderr=fnull)
|
|
os.unlink(tmp_file)
|
|
|
|
return result.returncode
|
|
|
|
def copyout(self, src, dst):
|
|
"""Unfortunately, to copy one file out entire LZX archive have to be
|
|
extracted. For small archives is not a problem, but in relatively big
|
|
one it could be a performance issue."""
|
|
tmp_dir = tempfile.mkdtemp()
|
|
src = [e['display_name'] for e in self._contents
|
|
if e['display_name'] == src]
|
|
if not src:
|
|
raise IOError("No such file or directory")
|
|
|
|
src = src[0].encode('latin-1')
|
|
|
|
current_dir = os.path.abspath(os.curdir)
|
|
os.chdir(tmp_dir)
|
|
|
|
with open(os.devnull, "w") as fnull:
|
|
result = subprocess.run([self.ARCHIVER, self.CMDS['read'],
|
|
os.path.join(current_dir, self._arch)],
|
|
stdout=fnull, stderr=fnull)
|
|
if result.returncode == 0:
|
|
# use subprocess, as shutil.copy2 will complain about mixing
|
|
# strings with bytes
|
|
subprocess.run(['cp', src, dst])
|
|
|
|
shutil.rmtree(tmp_dir)
|
|
os.chdir(current_dir)
|
|
|
|
return result.returncode
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(extfslib.parse_args(ULzx))
|