Files
mc_ulzx/ulzx

138 lines
4.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Read only, Amiga LZX[1] archiver Virtual filesystem executive for Midnight
Commander.
Tested against python 3.6, unlzx[1] 1.1 and mc 4.8.22
[1] ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme
Changelog:
1.2 Use python3
1.1 Moved common code into extfslib library
1.0 Initial release
Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2019-06-30
Version: 1.2
Licence: BSD
"""
import os
import sys
import re
import shutil
from subprocess import call, CalledProcessError
from tempfile import mkdtemp, mkstemp
from extfslib import Archive, parse_args
class ULzx(Archive):
"""Archive handle. Provides interface to MC's extfs subsystem"""
LINE_PAT = re.compile(b"^\s+(?P<size>\d+)\s+"
b"((n/a)|\d+)\s"
b"(?P<time>\d{2}:\d{2}:\d{2})\s+"
b"(?P<date>\d+-[a-z]{3}-\d{4})\s"
b"(?P<perms>[h-][s-][p-][a-][r-][w-][e-][d-])\s"
b"\"(?P<fpath>.*)\"")
ARCHIVER = b"unlzx"
CMDS = {"list": b"-v",
"read": b"-x"}
DATETIME = b"%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 = [b"jan", b"feb", b"mar", b"apr", b"may", b"jun", b"jul",
b"aug", b"sep", b"oct", b"nov", b"dec"]
day, month, year = date.split(b"-")
month = month_list.index(month) + 1
hours, minutes, dummy = time.split(b":")
return self.DATETIME % (month, int(day), year, int(hours),
int(minutes))
def _get_dir(self):
"""Prepare archive file listing"""
contents = []
out = self._call_command("list")
if not out:
return
for line in out.split(b"\n"):
match = self.LINE_PAT.match(line)
if not match:
continue
match_entry = match.groupdict()
entry = {}
for key in match_entry:
entry[bytes(key, 'utf-8')] = match_entry[key]
del match_entry
entry[b'datetime'] = self._get_date(entry[b'time'], entry[b'date'])
entry[b'display_name'] = self._map_name(entry[b'fpath'])
entry[b'perms'] = b"-rw-r--r--" # lzx doesn't store empty dirs
entry[b'uid'] = bytes(str(self._uid), 'utf-8')
entry[b'gid'] = bytes(str(self._gid), 'utf-8')
contents.append(entry)
return contents
def list(self):
"""Output contents of the archive to stdout"""
for entry in self._contents:
sys.stdout.buffer.write(self.ITEM % entry)
return 0
def run(self, dst):
"""Execute file out of archive"""
fdesc, tmp_file = mkstemp()
os.close(fdesc)
result = 0
if self.copyout(dst, tmp_file) != 0:
result = 1
os.chmod(tmp_file, int("700", 8))
try:
result = call([tmp_file])
finally:
try:
os.unlink(tmp_file)
except OSError:
pass
return result
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 = mkdtemp()
src = self._get_real_name(src)
current_dir = os.path.abspath(os.curdir)
os.chdir(tmp_dir)
try:
with open(os.devnull, "w") as fnull:
result = call([self.ARCHIVER, self.CMDS['read'],
os.path.join(current_dir, self._arch)],
stdout=fnull, stderr=fnull)
if result == 0:
# use subprocess, as shutil.copy2 will complain about mixing
# strings with bytes
subprocess.run(['cp', src, dst])
except CalledProcessError:
return 1
finally:
shutil.rmtree(tmp_dir)
os.chdir(current_dir)
return result
if __name__ == "__main__":
sys.exit(parse_args(ULzx))