Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01b4bf7a41 | |||
| 6b8684b1d7 | |||
| 2e50401a18 | |||
| f0a9e5f85d | |||
| 3d269303d9 | |||
| 0a972a5bce | |||
| e6e8b5c74a | |||
| b84d191632 | |||
| ffb99d6515 | |||
| c8407ff57b |
24
LICENSE
Normal file
24
LICENSE
Normal 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.
|
||||||
95
README.rst
95
README.rst
@@ -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.
|
||||||
|
|||||||
104
extfslib.py
104
extfslib.py
@@ -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
85
uadf
@@ -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
82
ulha
@@ -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
61
ulzx
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user