10 Commits
1.1 ... 1.2

Author SHA1 Message Date
01b4bf7a41 Adapt ulzx to python3 2019-06-30 19:18:03 +02:00
6b8684b1d7 Update uadf to support py3 2019-06-30 17:06:56 +02:00
2e50401a18 Bump version 2019-06-30 16:52:33 +02:00
f0a9e5f85d Fixes for ulha rmdir and copyin commands 2019-06-30 15:51:41 +02:00
3d269303d9 Added support for Python 3 for ulha
ulzx and uadf currently are broken. Fix for both of them is on the way.
2019-06-27 21:38:53 +02:00
0a972a5bce Readme update 2015-09-03 19:54:57 +02:00
e6e8b5c74a Added missing license information 2015-09-02 21:23:54 +02:00
b84d191632 Fixed destination directory for plugins in docs. 2015-09-02 21:20:32 +02:00
ffb99d6515 Moved common attributes to extfslib, uadf now will complain on nodos or
corrupted images.
2013-05-16 22:25:28 +02:00
c8407ff57b Reformated README 2013-05-12 18:23:49 +02:00
6 changed files with 300 additions and 151 deletions

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
Copyright (c) 2013, Roman Dobosz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the organization nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ROMAN DOBOSZ BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -2,13 +2,16 @@
Midnight Commander extfs Midnight Commander extfs
======================== ========================
Those are Midnight Commander extfs plugins for handling several archive types. Those are Midnight Commander extfs plugins for handling several archive types
mostly known from AmigaOS - like **lha**, **lzx** and disk images like **adf**
and **dms**.
Installation Installation
============ ============
See individual installation plugins below. Basically it come down to:
* copying ``extfslib.py`` and plugin files to ``~/.local/share/mc/extfs/`` See individual installation plugins below. Basically it comes down to:
* copying ``extfslib.py`` and plugin files to ``~/.local/share/mc/extfs.d/``
* installing binary handlers (lha, unlzx, xdms and unadf) * installing binary handlers (lha, unlzx, xdms and unadf)
* adding an entry in ``~/.config/mc/mc.ext``:: * adding an entry in ``~/.config/mc/mc.ext``::
@@ -18,17 +21,20 @@ See individual installation plugins below. Basically it come down to:
ULha ULha
==== ====
ULha is an extfs plugin which can be used with lha/lzh/lharc archives. ULha is an extfs plugin which can be used with lha/lzh/lharc archives.
Personally, I've use it almost exclusively for archives created long time ago Personally, I've use it almost exclusively for archives created long time ago
on my Amiga. Both reading and writing into archive was implemented. on my Amiga. Both reading from and writing into archive was implemented.
Requirements Requirements
------------ ------------
ULha requires `free lha <http://lha.sourceforge.jp>`_ implementation to work. ULha requires `free lha <http://lha.sourceforge.jp>`_ implementation to work.
Installation Installation
------------ ------------
* copy ``extfslib.py`` and ``ulha`` to ``~/.local/share/mc/extfs/``
* copy ``extfslib.py`` and ``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 # lha
@@ -38,25 +44,28 @@ Installation
ULzx ULzx
==== ====
ULzx is an extfs plugin which can be used to browse and extract lzx archives, ULzx is an extfs plugin which can be used to browse and extract lzx archives,
which are known almost exclusively from Amiga. which are known almost exclusively from Amiga.
Due to limitations of Due to limitations of
`unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ tools, `unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ tools,
only reading is supported. Also be aware, that only reading is supported. Also be aware, that
`unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ tool `unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ cannot
cannot extract files individually, so copying entire archive content is not extract files individually, so copying entire archive content is not
recommended, since on every file a full archive extract would be performed, recommended, since on every single file a full archive extract would be
which in the end would have impact on performance. performed, which in the end would have impact on performance.
Requirements Requirements
------------ ------------
ULzx requires ULzx requires
`unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ tool. `unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ tool.
Installation Installation
------------ ------------
* copy ``extfslib.py`` and ``ulzx`` to ``~/.local/share/mc/extfs/``
* copy ``extfslib.py`` and ``ulzx`` 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``::
# lzx # lzx
@@ -66,53 +75,63 @@ Installation
UAdf UAdf
==== ====
UAdf is an extfs plugin suitable for reading .adf, .adz and .dms Amiga floppy UAdf is an extfs plugin suitable for reading .adf, .adz and .dms Amiga floppy
disk images. Due to limitations of the disk images. Due to limitations of the
`unadf <http://freecode.com/projects/unadf>`_, file access inside disk image is `unadf <http://freecode.com/projects/unadf>`_, file access inside disk image is
read only. read only.
Note, that in case of no-dos images, directory listing will be empty. In case of corrupted or no-dos images, message will be shown.
Requirements Requirements
------------ ------------
It requires the `unadf <http://freecode.com/projects/unadf>`_ utility. It requires ``unadf`` utility from `ADFlib <https://github.com/lclevy/ADFlib>`_
Unfortunately, the page containing original sources doesn't exists repository, with included `that commit
anymore. Luckily, there is a copy of the source (and useful patches) in `Debian <https://github.com/lclevy/ADFlib/commit/d36dc2f395f3e8fcee81f66bc86994e166b6140f>`_
repository <http://packages.debian.org/sid/unadf>`_. in particular, which introduced separation between filename and comment
attribute on Amiga Fast File System.
There should be one change made to the source of unadf, though. While using If it turns out that your distribution doesn't provide proper version of ADFlib,
"-lr" switch, unadf by default also displays comments next to file name, there will be a need for building it by hand.
separated by the comma. If comment or filename contains comma sign, there is no
way to distinguish where filename ends and comment starts. In ``extras``
directory there is a patch for fixing this - no comments would be displayed by
default.
First, grab the sources and Debian patches. Apply them. To do this manually, It may be done by using following steps:
extract ``unadf_0.7.11a-3.debian.tar.gz`` and ``unadf_0.7.11a.orig.tar.gz`` into
some temporary directory. Apply patches in order like in debian/patches/series #. Grab the `sources
file. Then apply patch from extras directory:: <http://http.debian.net/debian/pool/main/u/unadf/unadf_0.7.11a.orig.tar.gz>`_
and `patches
<http://http.debian.net/debian/pool/main/u/unadf/unadf_0.7.11a-3.debian.tar.gz>`_
from `Debian repository <http://packages.debian.org/sid/unadf>`_.
#. Extract ``unadf_0.7.11a-3.debian.tar.gz`` and ``unadf_0.7.11a.orig.tar.gz``
into some temporary directory::
$ mkdir temp
$ cd temp
$ tar zxf ~/Downloads/unadf_0.7.11a-3.debian.tar.gz
$ tar zxf ~/Downloads/unadf_0.7.11a.orig.tar.gz
$ cd unadf-0.7.11a
#. Apply Debian patches::
$ mkdir temp
$ cd temp
$ tar zxf ~/Downloads/unadf_0.7.11a-3.debian.tar.gz
$ tar zxf ~/Downloads/unadf_0.7.11a.orig.tar.gz
$ cd unadf-0.7.11a
$ for i in `cat ../debian/patches/series`; do $ for i in `cat ../debian/patches/series`; do
> patch -Np1 < "../debian/patches/${i}" > patch -Np1 < "../debian/patches/${i}"
> done > done
$ patch -Np1 < [path_to_this_repo]/extras/unadf_separate_comment.patch
$ make
$ cp Demo/unadf [destination_path]
``unadf`` binary should be placed in ``$PATH``. #. Apply the patch from extras directory::
$ patch -Np1 < [path_to_this_repo]/extras/unadf_separate_comment.patch
$ make
$ cp Demo/unadf [destination_path]
#. Place ``unadf`` binary under directory reachable by ``$PATH``.
For optional dms support, `xdms <http://zakalwe.fi/~shd/foss/xdms/>`_ utility is For optional dms support, `xdms <http://zakalwe.fi/~shd/foss/xdms/>`_ utility is
needed. needed.
Installation Installation
------------ ------------
* copy ``extfslib.py`` and ``uadf`` to ``~/.local/share/mc/extfs/``
* copy ``extfslib.py`` and ``uadf`` 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``::
# adf # adf
@@ -127,3 +146,9 @@ Installation
# dms # dms
regex/\.([dD][mM][sS])$ regex/\.([dD][mM][sS])$
Open=%cd %p/uadf:// Open=%cd %p/uadf://
License
=======
This software is licensed under 3-clause BSD license. See LICENSE file for
details.

View File

@@ -2,16 +2,19 @@
extfslib is a library which contains Archive class to support writing extfs extfslib is a library which contains Archive class to support writing extfs
plugins for Midnight Commander. plugins for Midnight Commander.
Tested against python 2.7 and mc 4.8.7 Tested against python 3.6 and mc 4.8.22
Changelog: Changelog:
1.2 Switch to python3
1.1 Added item pattern, and common git/uid attrs
1.0 Initial release 1.0 Initial release
Author: Roman 'gryf' Dobosz <gryf73@gmail.com> Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2013-05-12 Date: 2019-06-30
Version: 1.0 Version: 1.2
Licence: BSD Licence: BSD
""" """
import argparse
import os import os
import sys import sys
import re import re
@@ -20,32 +23,37 @@ from subprocess import check_output, CalledProcessError
class Archive(object): class Archive(object):
"""Archive handle. Provides interface to MC's extfs subsystem""" """Archive handle. Provides interface to MC's extfs subsystem"""
LINE_PAT = re.compile("^(?P<size>)\s" LINE_PAT = re.compile(b"^(?P<size>)\s"
"(?P<perms>)\s" b"(?P<perms>)\s"
"(?P<uid>)\s" b"(?P<uid>)\s"
"(?P<gid>)\s" b"(?P<gid>)\s"
"(?P<date>)\s+" b"(?P<date>)\s+"
"(?P<time>)\s" b"(?P<time>)\s"
"(?P<fpath>)") b"(?P<fpath>)")
ARCHIVER = "archiver_name" ARCHIVER = b"archiver_name"
CMDS = {"list": "l", CMDS = {"list": b"l",
"read": "r", "read": b"r",
"write": "w", "write": b"w",
"delete": "d"} "delete": b"d"}
ITEM = (b"%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s %(datetime)s "
b"%(display_name)s\n")
def __init__(self, fname): def __init__(self, fname):
"""Prepare archive content for operations""" """Prepare archive content for operations"""
if not os.path.exists(fname): if not os.path.exists(fname):
raise OSError("No such file or directory `%s'" % fname) raise OSError("No such file or directory `%s'" % fname)
self._uid = os.getuid()
self._gid = os.getgid()
self._arch = fname self._arch = fname
self.name_map = {}
self._contents = self._get_dir() self._contents = self._get_dir()
def _map_name(self, name): def _map_name(self, name):
"""MC still have a bug in extfs subsystem, in case of filepaths with """MC still have a bug in extfs subsystem, in case of filepaths with
leading space. This is workaround to this bug, which replaces leading leading space. This is workaround to this bug, which replaces leading
space with tilda.""" space with tilda."""
if name.startswith(" "): if name.startswith(b" "):
new_name = "".join(["~", name[1:]]) new_name = b"".join([b"~", name[1:]])
return new_name return new_name
return name return name
@@ -53,8 +61,9 @@ class Archive(object):
"""Get real filepath of the file. See _map_name docstring for """Get real filepath of the file. See _map_name docstring for
details.""" details."""
for item in self._contents: for item in self._contents:
if item['display_name'] == name: if item[b'display_name'] == name.encode('utf-8',
return item['fpath'] 'surrogateescape'):
return item[b'fpath']
return None return None
def _get_dir(self): def _get_dir(self):
@@ -67,7 +76,7 @@ class Archive(object):
if not out: if not out:
return return
for line in out.split("\n"): for line in out.split(b"\n"):
match = self.LINE_PAT.match(line) match = self.LINE_PAT.match(line)
if not match: if not match:
continue continue
@@ -142,6 +151,61 @@ def usage():
{"prg": sys.argv[0]}) {"prg": sys.argv[0]})
def _parse_args(arch_class):
"""Use ArgumentParser to check for script arguments and execute."""
CALL_MAP = {'list': lambda a: arch_class(a.arch).list(),
'copyin': lambda a: arch_class(a.arch).copyin(a.src, a.dst),
'copyout': lambda a: arch_class(a.arch).copyout(a.src, a.dst),
'mkdir': lambda a: arch_class(a.arch).mkdir(a.dst),
'rm': lambda a: arch_class(a.arch).rm(a.dst),
'run': lambda a: arch_class(a.arch).run(a.dst)}
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='supported commands')
parser_list = subparsers.add_parser('list', help="List contents of "
"archive")
parser_copyin = subparsers.add_parser('copyin', help="Copy file into "
"archive")
parser_copyout = subparsers.add_parser('copyout', help="Copy file out of "
"archive")
parser_rm = subparsers.add_parser('rm', help="Delete file from archive")
parser_mkdir = subparsers.add_parser('mkdir', help="Create directory in "
"archive")
parser_run = subparsers.add_parser('run', help="Execute archived file")
parser_list.add_argument('arch', help="Archive filename")
parser_list.set_defaults(func=CALL_MAP['list'])
parser_copyin.add_argument('arch', help="Archive filename")
parser_copyin.add_argument('src', help="Source filename")
parser_copyin.add_argument('dst', help="Destination filename (to be "
"written into archive)")
parser_copyin.set_defaults(func=CALL_MAP['copyin'])
parser_copyout.add_argument('arch', help="D64 Image filename")
parser_copyout.add_argument('src', help="Source filename (to be read from"
" archive")
parser_copyout.add_argument('dst', help="Destination filename")
parser_copyout.set_defaults(func=CALL_MAP['copyout'])
parser_rm.add_argument('arch', help="D64 Image filename")
parser_rm.add_argument('dst', help="File inside archive to be deleted")
parser_rm.set_defaults(func=CALL_MAP['rm'])
parser_mkdir.add_argument('arch', help="archive filename")
parser_mkdir.add_argument('dst', help="Directory name inside archive to "
"be created")
parser_mkdir.set_defaults(func=CALL_MAP['mkdir'])
parser_run.add_argument('arch', help="archive filename")
parser_run.add_argument('dst', help="File to be executed")
parser_run.set_defaults(func=CALL_MAP['run'])
args = parser.parse_args()
return args.func(args)
def parse_args(arch_class): def parse_args(arch_class):
"""Retrive and parse arguments from commandline and apply them into passed """Retrive and parse arguments from commandline and apply them into passed
arch_class class object.""" arch_class class object."""

85
uadf
View File

@@ -1,11 +1,11 @@
#! /usr/bin/env python #!/usr/bin/env python3
""" """
UADF Virtual filesystem UADF Virtual filesystem
This extfs provides quick and dirty read-only access to disk image files for This extfs provides quick and dirty read-only access to disk image files for
the Commodore Amiga adf or adz (gzipped adfs) and dms. the Commodore Amiga adf or adz (gzipped adfs) and dms.
It requires the unadf utility (unfortunately there is no original sources, It requires the unadf utility, unfortunately there is no original sources,
since authors page doesn't exists anymore. Luckily, there is a copy of the since authors page doesn't exists anymore. Luckily, there is a copy of the
source (and useful patches) in Debian repository: source (and useful patches) in Debian repository:
http://packages.debian.org/sid/unadf http://packages.debian.org/sid/unadf
@@ -15,15 +15,19 @@ There should be one change made to the source of unadf, though. While using
However there is no way to distinguish where filename ends and comment starts, However there is no way to distinguish where filename ends and comment starts,
if comment or filename already contains any comma. if comment or filename already contains any comma.
The patched sources are available from: https://github.com/lclevy/ADFlib
It also requires xdms utility, for optional dms support. It also requires xdms utility, for optional dms support.
Changelog: Changelog:
1.3 Switch to Python3
1.2 Added failsafe for filenames in archive with spaces and nodos message.
1.1 Moved common code into extfslib library 1.1 Moved common code into extfslib library
1.0 Initial release 1.0 Initial release
Author: Roman 'gryf' Dobosz <gryf73@gmail.com> Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2013-05-12 Date: 2019-06-30
Version: 1.1 Version: 1.3
Licence: BSD Licence: BSD
""" """
@@ -42,19 +46,17 @@ class UAdf(Archive):
""" """
Class for interact with c1541 program and MC Class for interact with c1541 program and MC
""" """
LINE_PAT = re.compile('\s*(?P<size>\d+)?' LINE_PAT = re.compile(b'\s*(?P<size>\d+)?'
'\s{2}(?P<date>\d{4}/\d{2}/\d{2})' b'\s{2}(?P<date>\d{4}/\d{2}/\d{2})'
'\s{2}\s?(?P<time>\d+:\d{2}:\d{2})' b'\s{2}\s?(?P<time>\d+:\d{2}:\d{2})'
'\s{2}(?P<fpath>.*)') b'\s{2}(?P<fpath>.*)')
ARCHIVER = "unadf" ARCHIVER = b"unadf"
DMS = "xdms" DMS = b"xdms"
CMDS = {"list": "-lr", CMDS = {"list": b"-lr",
"read": "r", "read": b"r",
"write": "w", "write": b"w",
"delete": "d"} "delete": b"d"}
DATETIME = "%s-%s-%s %02d:%s" DATETIME = b"%s-%s-%s %02d:%s"
ITEM = ("%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s %(datetime)s "
"%(display_name)s\n")
def __init__(self, fname): def __init__(self, fname):
"""Prepare archive content for operations""" """Prepare archive content for operations"""
@@ -79,8 +81,8 @@ class UAdf(Archive):
def _parse_dt(self, date, time): def _parse_dt(self, date, time):
"""Return parsed datetime which fulfill extfs standards date.""" """Return parsed datetime which fulfill extfs standards date."""
year, month, day = date.split("/") year, month, day = date.split(b"/")
hours, minutes, _unused = time.split(":") hours, minutes, _unused = time.split(b":")
return self.DATETIME % (month, day, year, int(hours), minutes) return self.DATETIME % (month, day, year, int(hours), minutes)
def _ungzip(self): def _ungzip(self):
@@ -101,7 +103,7 @@ class UAdf(Archive):
os.close(fdesc) os.close(fdesc)
try: try:
check_call([self.DMS, '-q', 'u', self._arch, "+" + tmp_fname]) check_call([self.DMS, b'-q', b'u', self._arch, "+" + tmp_fname])
self._arch = tmp_fname self._arch = tmp_fname
self._clean = False self._clean = False
except (CalledProcessError, OSError): except (CalledProcessError, OSError):
@@ -117,20 +119,25 @@ class UAdf(Archive):
except CalledProcessError: except CalledProcessError:
return contents return contents
for line in out.split("\n"): for line in out.split(b"\n"):
match = self.LINE_PAT.match(line) match = self.LINE_PAT.match(line)
if not match: if not match:
continue continue
entry = match.groupdict() match_entry = match.groupdict()
entry['perms'] = "-rw-r--r--" entry = {}
if not entry['size']: for key in match_entry:
entry['perms'] = "drwxr-xr-x" entry[bytes(key, 'utf-8')] = match_entry[key]
entry['size'] = "0" del match_entry
entry['display_name'] = self._map_name(entry['fpath'])
entry['datetime'] = self._parse_dt(entry['date'], entry['time']) entry[b'perms'] = b"-rw-r--r--"
entry['uid'] = self._uid if not entry[b'size']:
entry['gid'] = self._gid entry[b'perms'] = b"drwxr-xr-x"
entry[b'size'] = b"0"
entry[b'display_name'] = self._map_name(entry[b'fpath'])
entry[b'datetime'] = self._parse_dt(entry[b'date'], entry[b'time'])
entry[b'uid'] = bytes(str(self._uid), 'utf-8')
entry[b'gid'] = bytes(str(self._gid), 'utf-8')
contents.append(entry) contents.append(entry)
return contents return contents
@@ -141,8 +148,12 @@ class UAdf(Archive):
Convert filenames to be Unix filesystem friendly Convert filenames to be Unix filesystem friendly
Add suffix to show user what kind of file do he dealing with. Add suffix to show user what kind of file do he dealing with.
""" """
if not self._contents:
sys.stderr.write("Nodos or archive error\n")
return 1
for entry in self._contents: for entry in self._contents:
sys.stdout.write(self.ITEM % entry) sys.stdout.buffer.write(self.ITEM % entry)
return 0 return 0
def copyout(self, src, dst): def copyout(self, src, dst):
@@ -151,18 +162,26 @@ class UAdf(Archive):
if not real_src: if not real_src:
raise IOError("No such file or directory") raise IOError("No such file or directory")
if b" " in real_src:
sys.stderr.write("unadf is unable to operate on filepath with "
"space inside.\nUse affs to mount image and than"
" extract desired files.\n")
return 1
extract_dir = mkdtemp() extract_dir = mkdtemp()
cmd = ["unadf", self._arch, real_src, "-d", extract_dir] cmd = [self.ARCHIVER, self._arch, real_src, b"-d", extract_dir]
if check_call(cmd, stdout=open(os.devnull, 'wb'), if check_call(cmd, stdout=open(os.devnull, 'wb'),
stderr=open(os.devnull, 'wb')) != 0: stderr=open(os.devnull, 'wb')) != 0:
shutil.rmtree(extract_dir) shutil.rmtree(extract_dir)
sys.stderr.write("unadf returned with nonzero exit code\n") sys.stderr.write("unadf returned with nonzero exit code\n")
return 1 return 1
shutil.move(os.path.join(extract_dir, real_src), dst) shutil.move(os.path.join(bytes(extract_dir, "utf8"), real_src),
bytes(dst, "utf8"))
shutil.rmtree(extract_dir) shutil.rmtree(extract_dir)
return 0 return 0
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(parse_args(UAdf)) sys.exit(parse_args(UAdf))

82
ulha
View File

@@ -1,18 +1,20 @@
#! /usr/bin/env python #!/usr/bin/env python3
""" """
Lha Virtual filesystem executive for Midnight Commander. Lha Virtual filesystem executive for Midnight Commander.
Tested against python 2.7, lha[1] 1.14 and mc 4.8.7 Tested against python 3.6, lha[1] 1.14 and mc 4.8.22
[1] http://lha.sourceforge.jp [1] http://lha.sourceforge.jp
Changelog: Changelog:
1.3 Switch to python3
1.2 Moved item pattern to extfslib module
1.1 Moved common code into extfslib library 1.1 Moved common code into extfslib library
1.0 Initial release 1.0 Initial release
Author: Roman 'gryf' Dobosz <gryf73@gmail.com> Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2013-05-12 Date: 2019-06-30
Version: 1.1 Version: 1.3
Licence: BSD Licence: BSD
""" """
import os import os
@@ -28,23 +30,22 @@ from extfslib import Archive, parse_args
class ULha(Archive): class ULha(Archive):
"""Archive handle. Provides interface to MC's extfs subsystem""" """Archive handle. Provides interface to MC's extfs subsystem"""
LINE_PAT = re.compile("^((?P<perms>[d-][rswx-]{9})|(\[generic\])|" LINE_PAT = re.compile(b"^((?P<perms>[d-][rswx-]{9})|(\[generic\])|"
"(\[unknown\]))" b"(\[unknown\]))"
"((\s+\d+/\d+\s+)|(\s+))" b"((\s+\d+/\d+\s+)|(\s+))"
"(?P<uid>)(?P<gid>)" # just for the record b"(?P<uid>)(?P<gid>)" # just for the record
"(?P<size>\d+)" b"(?P<size>\d+)"
"\s+(\*{6}|\d+\.\d%)" b"\s+(\*{6}|\d+\.\d%)"
"\s(?P<month>[JFMASOND][a-z]{2})\s+" # month b"\s(?P<month>[JFMASOND][a-z]{2})\s+" # month
"(?P<day>\d+)\s+" # day b"(?P<day>\d+)\s+" # day
"(?P<yh>\d{4}|(\d{2}:\d{2}))" # year/hour b"(?P<yh>\d{4}|(\d{2}:\d{2}))" # year/hour
"\s(?P<fpath>.*)") b"\s(?P<fpath>.*)")
ARCHIVER = "lha" ARCHIVER = b"lha"
CMDS = {"list": "lq", CMDS = {"list": b"lq",
"read": "pq", "read": b"pq",
"write": "aq", "write": b"aq",
"delete": "dq"} "delete": b"dq"}
ITEM = ("%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s %(month)s %(day)s " DATETIME = b"%(month)s %(day)s %(yh)s"
"%(yh)s %(display_name)s\n")
def _get_dir(self): def _get_dir(self):
"""Prepare archive file listing""" """Prepare archive file listing"""
@@ -54,36 +55,43 @@ class ULha(Archive):
if not out: if not out:
return return
for line in out.split("\n"): for line in out.split(b"\n"):
# -lhd- can store empty directories # -lhd- can store empty directories
perms = "-rw-r--r--" perms = b"-rw-r--r--"
if line.endswith(os.path.sep): if line.endswith(bytes(os.path.sep, 'utf-8')):
line = line[:-1] line = line[:-1]
perms = "drw-r--r--" perms = b"drw-r--r--"
match = self.LINE_PAT.match(line) match = self.LINE_PAT.match(line)
if not match: if not match:
continue continue
entry = match.groupdict() match_entry = match.groupdict()
entry = {}
for key in match_entry:
entry[bytes(key, 'utf-8')] = match_entry[key]
del match_entry
# UID and GID sometimes can have strange values depending on # UID and GID sometimes can have strange values depending on
# the information that was written into archive. Most of the # the information that was written into archive. Most of the
# times I was dealing with Amiga lha archives, so that i don't # times I was dealing with Amiga lha archives, so that i don't
# really care about real user/group # really care about real user/group
entry['uid'] = self._uid
entry['gid'] = self._gid
if not entry['perms']: entry[b'uid'] = bytes(str(self._uid), 'utf-8')
entry['perms'] = perms entry[b'gid'] = bytes(str(self._gid), 'utf-8')
entry[b'datetime'] = self.DATETIME % entry
entry['display_name'] = self._map_name(entry['fpath']) if not entry[b'perms']:
entry[b'perms'] = perms
entry[b'display_name'] = self._map_name(entry[b'fpath'])
contents.append(entry) contents.append(entry)
return contents return contents
def list(self): def list(self):
"""Output contents of the archive to stdout""" """Output contents of the archive to stdout"""
for entry in self._contents: for entry in self._contents:
sys.stdout.write(self.ITEM % entry) sys.stdout.buffer.write(self.ITEM % entry)
return 0 return 0
def rm(self, dst): def rm(self, dst):
@@ -98,8 +106,9 @@ class ULha(Archive):
def rmdir(self, dst): def rmdir(self, dst):
"""Remove empty directory""" """Remove empty directory"""
dst = self._get_real_name(dst) dst = self._get_real_name(dst)
if not dst.endswith(os.path.sep):
dst += os.path.sep if not dst.endswith(bytes(os.path.sep, 'utf-8')):
dst += bytes(os.path.sep, 'utf-8')
if self._call_command('delete', dst=dst) is None: if self._call_command('delete', dst=dst) is None:
return 1 return 1
@@ -140,12 +149,13 @@ class ULha(Archive):
os.chdir(tmpdir) os.chdir(tmpdir)
if src: if src:
os.makedirs(os.path.dirname(dst)) os.makedirs(os.path.dirname(dst))
os.link(src, dst) shutil.copy2(src, dst)
else: else:
os.makedirs(dst) os.makedirs(dst)
try: try:
result = check_call([self.ARCHIVER, self.CMDS["write"], result = check_call([self.ARCHIVER.decode('utf-8'),
self.CMDS["write"].decode('utf-8'),
arch_abspath, dst]) arch_abspath, dst])
except CalledProcessError: except CalledProcessError:
return 1 return 1

61
ulzx
View File

@@ -1,19 +1,20 @@
#! /usr/bin/env python #!/usr/bin/env python3
""" """
Read only, Amiga LZX[1] archiver Virtual filesystem executive for Midnight Read only, Amiga LZX[1] archiver Virtual filesystem executive for Midnight
Commander. Commander.
Tested against python 2.7, unlzx[1] 1.1 and mc 4.8.7 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 [1] ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme
Changelog: Changelog:
1.2 Use python3
1.1 Moved common code into extfslib library 1.1 Moved common code into extfslib library
1.0 Initial release 1.0 Initial release
Author: Roman 'gryf' Dobosz <gryf73@gmail.com> Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2013-05-12 Date: 2019-06-30
Version: 1.1 Version: 1.2
Licence: BSD Licence: BSD
""" """
import os import os
@@ -28,26 +29,27 @@ from extfslib import Archive, parse_args
class ULzx(Archive): class ULzx(Archive):
"""Archive handle. Provides interface to MC's extfs subsystem""" """Archive handle. Provides interface to MC's extfs subsystem"""
LINE_PAT = re.compile("^\s+(?P<size>\d+)\s+" LINE_PAT = re.compile(b"^\s+(?P<size>\d+)\s+"
"((n/a)|\d+)\s" b"((n/a)|\d+)\s"
"(?P<time>\d{2}:\d{2}:\d{2})\s+" b"(?P<time>\d{2}:\d{2}:\d{2})\s+"
"(?P<date>\d+-[a-z]{3}-\d{4})\s" b"(?P<date>\d+-[a-z]{3}-\d{4})\s"
"(?P<perms>[h-][s-][p-][a-][r-][w-][e-][d-])\s" b"(?P<perms>[h-][s-][p-][a-][r-][w-][e-][d-])\s"
"\"(?P<fpath>.*)\"") b"\"(?P<fpath>.*)\"")
ARCHIVER = "unlzx" ARCHIVER = b"unlzx"
CMDS = {"list": "-v", CMDS = {"list": b"-v",
"read": "-x"} "read": b"-x"}
DATETIME = "%02d-%02d-%s %02d:%02d" DATETIME = b"%02d-%02d-%s %02d:%02d"
def _get_date(self, time, date): def _get_date(self, time, date):
"""Return MM-DD-YYYY hh:mm formatted date out of time and date """Return MM-DD-YYYY hh:mm formatted date out of time and date
strings""" strings"""
month_list = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", month_list = [b"jan", b"feb", b"mar", b"apr", b"may", b"jun", b"jul",
"sep", "oct", "nov", "dec"] b"aug", b"sep", b"oct", b"nov", b"dec"]
day, month, year = date.split("-") day, month, year = date.split(b"-")
month = month_list.index(month) + 1 month = month_list.index(month) + 1
hours, minutes, dummy = time.split(":") hours, minutes, dummy = time.split(b":")
return self.DATETIME % (month, int(day), year, int(hours), int(minutes)) return self.DATETIME % (month, int(day), year, int(hours),
int(minutes))
def _get_dir(self): def _get_dir(self):
"""Prepare archive file listing""" """Prepare archive file listing"""
@@ -57,17 +59,22 @@ class ULzx(Archive):
if not out: if not out:
return return
for line in out.split("\n"): for line in out.split(b"\n"):
match = self.LINE_PAT.match(line) match = self.LINE_PAT.match(line)
if not match: if not match:
continue continue
entry = match.groupdict() match_entry = match.groupdict()
entry['datetime'] = self._get_date(entry['time'], entry['date']) entry = {}
entry['display_name'] = self._map_name(entry['fpath']) for key in match_entry:
entry['perms'] = "-rw-r--r--" # lzx doesn't store empty dirs entry[bytes(key, 'utf-8')] = match_entry[key]
entry['uid'] = self._uid del match_entry
entry['gid'] = self._gid
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) contents.append(entry)
return contents return contents
@@ -75,7 +82,7 @@ class ULzx(Archive):
def list(self): def list(self):
"""Output contents of the archive to stdout""" """Output contents of the archive to stdout"""
for entry in self._contents: for entry in self._contents:
sys.stdout.write(self.ITEM % entry) sys.stdout.buffer.write(self.ITEM % entry)
return 0 return 0
def run(self, dst): def run(self, dst):