diff --git a/README.rst b/README.rst index 66ad71c..a17b8ed 100644 --- a/README.rst +++ b/README.rst @@ -14,19 +14,32 @@ on my Amiga. Both reading from and writing into archive was implemented. Requirements ------------ -ULha requires `free lha `_ implementation to work. +ULha requires free `lha`_ implementation to work. There is another lha +implementation under development - `lhasa`_, although it is currently limited +to list and extract only. + +Limitations +----------- + +For filenames which contain other characters outside of ASCII set, there is no +way to operate on them as `lha`_ simply ignore those files. Extracting full +archive will extract also those files, although names would be incorrectly +decoded, and hence, unusable on the AmigaOS. Looking forward for the `lhasa`_ +to be completed, and then will switch to it. Installation ------------ * install `extfslib`_ * copy ``ulha`` to ``~/.local/share/mc/extfs.d/`` -* add or change entry for files handle in ``~/.config/mc/mc.ext``:: +* add or change entry for files handle in ``~/.config/mc/mc.ext``: - # lha - regex/\.[lL]([Hh][aA]|[Zz][hH])$ - Open=%cd %p/ulha:// - View=%view{ascii} lha l %f +.. code::ini + + [lha] + Type=^LHa\ .*archive + Open=%cd %p/ulha:// + View=%view{ascii} /usr/libexec/mc/ext.d/archive.sh view lha License ======= @@ -36,3 +49,5 @@ details. .. _extfslib: https://github.com/gryf/mc_extfslib +.. _lha: http://lha.sourceforge.jp +.. _lhasa: https://github.com/fragglet/lhasa diff --git a/ulha b/ulha index 2f5b935..164da99 100755 --- a/ulha +++ b/ulha @@ -2,19 +2,20 @@ """ Lha Virtual filesystem executive for Midnight Commander. -Tested against python 3.6, lha[1] 1.14 and mc 4.8.22 +Tested against python 3.8, lha[1] 1.14 and mc 4.8.29 [1] http://lha.sourceforge.jp Changelog: + 1.4 Cleanup code, use latin1 1.3 Switch to python3 1.2 Moved item pattern to extfslib module 1.1 Moved common code into extfslib library 1.0 Initial release Author: Roman 'gryf' Dobosz -Date: 2019-06-30 -Version: 1.3 +Date: 2023-10-22 +Version: 1.4 Licence: BSD """ import os @@ -30,37 +31,39 @@ import extfslib class ULha(extfslib.Archive): """Archive handle. Provides interface to MC's extfs subsystem""" - LINE_PAT = re.compile(b"^((?P[d-][rswx-]{9})|(\[generic\])|" - b"(\[unknown\]))" - b"((\s+\d+/\d+\s+)|(\s+))" - b"(?P)(?P)" # just for the record - b"(?P\d+)" - b"\s+(\*{6}|\d+\.\d%)" - b"\s(?P[JFMASOND][a-z]{2})\s+" # month - b"(?P\d+)\s+" # day - b"(?P\d{4}|(\d{2}:\d{2}))" # year/hour - b"\s(?P.*)") - ARCHIVER = b"lha" - CMDS = {"list": b"lq", - "read": b"pq", - "write": b"aq", - "delete": b"dq"} - DATETIME = b"%(month)s %(day)s %(yh)s" + LINE_PAT = re.compile(r"^((?P[d-][rswx-]{9})|(\[generic\])|" + r"(\[unknown\]))" + r"((\s+\d+/\d+\s+)|(\s+))" + r"(?P)(?P)" # just for the record + r"(?P\d+)" + r"\s+(\*{6}|\d+\.\d%)" + r"\s(?P[JFMASOND][a-z]{2})\s+" # month + r"(?P\d+)\s+" # day + r"(?P\d{4}|(\d{2}:\d{2}))" # year/hour + r"\s(?P.*)") + ARCHIVER = "lha" + CMDS = {"list": "lq", + "read": "pq", + "write": "aq", + "delete": "dq"} + DATETIME = "%(month)s %(day)s %(yh)s" def _get_dir(self): """Prepare archive file listing""" contents = [] - out = self._call_command("list") - if not out: + out = subprocess.run([self.ARCHIVER, self.CMDS['list'], self._arch], + capture_output=True, encoding="Latin1") + if out.returncode != 0: + sys.stderr.write(out.stderr) return - for line in out.split(b"\n"): + for line in out.stdout.split("\n"): # -lhd- can store empty directories - perms = b"-rw-r--r--" - if line.endswith(bytes(os.path.sep, 'utf-8')): + perms = "-rw-r--r--" + if line.endswith(os.path.sep): line = line[:-1] - perms = b"drw-r--r--" + perms = "drw-r--r--" match = self.LINE_PAT.match(line) if not match: @@ -69,21 +72,21 @@ class ULha(extfslib.Archive): match_entry = match.groupdict() entry = {} for key in match_entry: - entry[bytes(key, 'utf-8')] = match_entry[key] + entry[key] = match_entry[key] del match_entry # UID and GID sometimes can have strange values depending on # the information that was written into archive. Most of the # times I was dealing with Amiga lha archives, so that i don't # really care about real user/group - entry[b'uid'] = bytes(str(self._uid), 'utf-8') - entry[b'gid'] = bytes(str(self._gid), 'utf-8') - entry[b'datetime'] = self.DATETIME % entry + entry['uid'] = str(self._uid) + entry['gid'] = str(self._gid) + entry['datetime'] = self.DATETIME % entry - if not entry[b'perms']: - entry[b'perms'] = perms + if not entry['perms']: + entry['perms'] = perms - entry[b'display_name'] = self._map_name(entry[b'fpath']) + entry['display_name'] = self._map_name(entry['fpath']) contents.append(entry) return contents @@ -91,7 +94,7 @@ class ULha(extfslib.Archive): def list(self): """Output contents of the archive to stdout""" for entry in self._contents: - sys.stdout.buffer.write(self.ITEM % entry) + sys.stdout.write(self.ITEM.decode('utf-8') % entry) return 0 def rm(self, dst): @@ -107,12 +110,12 @@ class ULha(extfslib.Archive): """Remove empty directory""" dst = self._get_real_name(dst) - if not dst.endswith(bytes(os.path.sep, 'utf-8')): - dst += bytes(os.path.sep, 'utf-8') + if not dst.endswith(os.path.sep): + dst += os.path.sep - if self._call_command('delete', dst=dst) is None: - return 1 - return 0 + res = subprocess.run([self.ARCHIVER, self.CMDS['delete'], dst], + capture_output=True) + return res.returncode def run(self, dst): """Execute file out of archive""" @@ -153,29 +156,19 @@ class ULha(extfslib.Archive): else: os.makedirs(dst) - try: - result = subprocess.check_call([self.ARCHIVER.decode('utf-8'), - self.CMDS["write"].decode('utf-8'), - arch_abspath, dst]) - except subprocess.CalledProcessError: - return 1 - finally: - os.chdir(current_dir) - shutil.rmtree(tmpdir) - return result + res = subprocess.run([self.ARCHIVER, self.CMDS["write"], arch_abspath, + dst], capture_output=True, encoding='utf-8') + os.chdir(current_dir) + shutil.rmtree(tmpdir) + return res.returncode def copyout(self, src, dst): """Copy file out form archive.""" src = self._get_real_name(src) - fobj = open(dst, "wb") - try: - result = subprocess.check_call([self.ARCHIVER, self.CMDS['read'], self._arch, - src], stdout=fobj) - except subprocess.CalledProcessError: - return 1 - finally: - fobj.close() - return result + with open(dst, "wb") as fobj: + res = subprocess.run([self.ARCHIVER, self.CMDS['read'], self._arch, + src], stdout=fobj) + return res.returncode if __name__ == "__main__":