19 Commits
1.0 ... master

Author SHA1 Message Date
8a04be49e7 Detect byts/string, and load config by default. 2023-10-22 18:45:41 +02:00
9ac9ae210d Readme update 2023-10-19 19:50:51 +02:00
970653c22d Added new class for configuration reading. 2023-10-19 19:50:33 +02:00
5f52e61a4e Normalize imports 2023-10-19 17:58:52 +02:00
b017ac4d5a Corrected couple of typos and missaligements 2023-10-18 21:37:10 +02:00
918a949605 Readme update. 2022-10-02 15:11:29 +02:00
91c31067d0 Readme update 2022-10-02 15:05:40 +02:00
6a127e452f Added setuptools related files. 2022-10-02 14:43:18 +02:00
44c9de2df9 Removed archive handlers, as they are on separate repos now. 2022-10-02 12:44:14 +02:00
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
9 changed files with 312 additions and 732 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

@@ -1,129 +1,124 @@
========================
Midnight Commander extfs
========================
===========================
Midnight Commander extfslib
===========================
.. image:: https://img.shields.io/pypi/v/extfslib.svg
:target: https://pypi.python.org/pypi/extfslib
Midnight Commander extfslib helper library for writing extfs archive plugins.
Description
===========
Extfslib help with building Midnight Commander extfs plugins, especially for
those which operates on different kind of archives.
Simplest plugin built on top of this lib would be:
.. code:: python
import extfslib
class MyArchive(extfslib.Archive):
ARCHIVER = "fancyarch"
def list(self):
if not self._contents:
return 1
for item in self._contents:
sys.stdout.buffer.write(self.ITEM % item)
arch = MyArchive('/path/to/file.fancyarch')
arch.list()
In this example class instance should be able to be called with ``list``
method. All methods:
- ``list``
- ``copyin``
- ``copyout``
- ``rm``
- ``mkdir``
- ``rmdir``
- ``run``
should be implemented if needed, since by default all of them are just defined,
but not implemented.
Of course, real life example can be a little bit more complicated, since there
would be possible need for adapting ``LINE_PAT`` which is regular expression
for getting attributes for the list compatible with MC along with the ``ITEM``
which holds the output pattern and utilizes dictionary from ``LINE_PAT``,
``CMD`` which maps between class and archiver commands. Possibly there might be
needed some other adjustments.
Additionally there is an optional ``Config`` class, which might be used for
reading Midnight Commander ini file (ususally located in ``~/.config/mc/ini``),
so that for the example above:
.. code:: python
import extfslib
class MyArchive(extfslib.Archive):
ARCHIVER = "fancyarch"
def __init__(self):
super().__init__()
self.conf = extfslib.Config(self)
if self.conf,getint('config_key'):
# do something
where the ``ini`` config file would contain:
.. code:: ini
…
[myarchive]
config_key = 300
…
So, section name ``[myarchive]`` must match class name in lower case, and name
of the option is arbitrary string folowed by value. Note, the section and
options must be added manually.
Those are Midnight Commander extfs plugins for handling several archive types.
Installation
============
See individual installation plugins below. Basically it come down to:
* copying ``extfslib.py`` and plugin files to ``~/.local/share/mc/extfs/``
* installing binary handlers (lha, unlzx, xdms and unadf)
* adding an entry in ``~/.config/mc/mc.ext``::
Install from Pypi
# arch
regex/\.pattern$
Open=%cd %p/handler_filename://
.. code:: shell-session
ULha
====
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
on my Amiga. Both reading and writing into archive was implemented.
# pip install extfslib
Requirements
------------
ULha requires `free lha <http://lha.sourceforge.jp>`_ implementation to work.
or, as a user:
Installation
------------
* copy ``extfslib.py`` and ``ulha`` to ``~/.local/share/mc/extfs/``
* add or change entry for files handle in ``~/.config/mc/mc.ext``::
.. code:: shell-session
# lha
regex/\.[lL]([Hh][aA]|[Zz][hH])$
Open=%cd %p/ulha://
View=%view{ascii} lha l %f
$ pip install extfslib --user
ULzx
====
ULzx is an extfs plugin which can be used to browse and extract lzx archives,
which are known almost exclusively from Amiga.
or use virtualenv:
Due to limitations of
`unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ tools,
only reading is supported. Also be aware, that
`unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ tool
cannot extract files individually, so copying entire archive content is not
recommended, since on every file a full archive extract would be performed,
which in the end would have impact on performance.
.. code:: shell-session
Requirements
------------
ULzx requires
`unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ tool.
$ git clone https://github.com/gryf/mc_extfslib
$ cd mc_extfslib
$ virtualenv venv
$ source venv/bin/activate
(venv) $ pip install
Installation
------------
* copy ``extfslib.py`` and ``ulzx`` to ``~/.local/share/mc/extfs/``
* add or change entry for files handle in ``~/.config/mc/mc.ext``::
# lzx
regex/\.[lL][zZ][xX]$
Open=%cd %p/ulzx://
View=%view{ascii} unlzx -v %f
License
=======
UAdf
====
UAdf is an extfs plugin suitable for reading .adf, .adz and .dms Amiga floppy
disk images. Due to limitations of the
`unadf <http://freecode.com/projects/unadf>`_, file access inside disk image is
read only.
Note, that in case of no-dos images, directory listing will be empty.
Requirements
------------
It requires the `unadf <http://freecode.com/projects/unadf>`_ utility.
Unfortunately, the page containing original sources doesn't exists
anymore. Luckily, there is a copy of the source (and useful patches) in `Debian
repository <http://packages.debian.org/sid/unadf>`_.
There should be one change made to the source of unadf, though. While using
"-lr" switch, unadf by default also displays comments next to file name,
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,
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
file. Then apply patch from extras 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
$ for i in `cat ../debian/patches/series`; do
> patch -Np1 < "../debian/patches/${i}"
> 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``.
For optional dms support, `xdms <http://zakalwe.fi/~shd/foss/xdms/>`_ utility is
needed.
Installation
------------
* copy ``extfslib.py`` and ``uadf`` to ``~/.local/share/mc/extfs/``
* add or change entry for files handle in ``~/.config/mc/mc.ext``::
# adf
type/^Amiga\ .* disk
Open=%cd %p/uadf://
View=%view{ascii} unadf -lr %f
# adz
regex/\.([aA][dD][zZ])$
Open=%cd %p/uadf://
# dms
regex/\.([dD][mM][sS])$
Open=%cd %p/uadf://
This software is licensed under 3-clause BSD license. See LICENSE file for
details.

View File

@@ -2,59 +2,125 @@
extfslib is a library which contains Archive class to support writing extfs
plugins for Midnight Commander.
Tested against python 2.7 and mc 4.8.7
Tested against python 3.11 and mc 4.8.29
Changelog:
1.4 Detect byts/string, and load config by default
1.3 Added ability to read config from mc/ini file.
1.2 Switch to python3
1.1 Added item pattern, and common git/uid attrs
1.0 Initial release
Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2013-05-12
Version: 1.0
Date: 2023-10-22
Version: 1.4
Licence: BSD
"""
import argparse
import configparser
import os
import sys
import re
from subprocess import check_output, CalledProcessError
import subprocess
import sys
XDG_CONF_DIR = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
class Config:
"""An optional config class, a helper to get and parse MC ini file"""
def __init__(self, caller):
self._config = self._get_config()
self._class_name = caller.__class__.__name__.lower()
def _get_config(self):
"""Read MC main config file"""
conf_file = os.path.join(XDG_CONF_DIR, 'mc/ini')
conf_parser = configparser.ConfigParser()
conf_parser.read(conf_file)
return conf_parser
def getboolean(self, name):
try:
return self._config.getboolean(self._class_name, name)
except (configparser.NoOptionError, configparser.NoSectionError):
pass
def getint(self, name):
try:
return self._config.getint(self._class_name, name)
except (configparser.NoOptionError, configparser.NoSectionError):
pass
def getfloat(self, name):
try:
return self._config.getfloat(self._class_name, name)
except (configparser.NoOptionError, configparser.NoSectionError):
pass
def get(self, name):
return getattr(self, name)
def __getattr__(self, name):
try:
return self._config.get(self._class_name, name)
except (configparser.NoOptionError, configparser.NoSectionError):
pass
class Archive(object):
"""Archive handle. Provides interface to MC's extfs subsystem"""
LINE_PAT = re.compile("^(?P<size>)\s"
"(?P<perms>)\s"
"(?P<uid>)\s"
"(?P<gid>)\s"
"(?P<date>)\s+"
"(?P<time>)\s"
"(?P<fpath>)")
ARCHIVER = "archiver_name"
CMDS = {"list": "l",
"read": "r",
"write": "w",
"delete": "d"}
LINE_PAT = re.compile(br"^(?P<size>)\s"
br"(?P<perms>)\s"
br"(?P<uid>)\s"
br"(?P<gid>)\s"
br"(?P<date>)\s+"
br"(?P<time>)\s"
br"(?P<fpath>)")
ARCHIVER = b"archiver_name"
CMDS = {"list": b"l",
"read": b"r",
"write": b"w",
"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):
"""Prepare archive content for operations"""
if not os.path.exists(fname):
raise OSError("No such file or directory `%s'" % fname)
self._uid = os.getuid()
self._gid = os.getgid()
self._arch = fname
self.name_map = {}
self._contents = self._get_dir()
self.config = Config(self)
def _map_name(self, name):
"""MC still have a bug in extfs subsystem, in case of filepaths with
leading space. This is workaround to this bug, which replaces leading
space with tilda."""
if name.startswith(" "):
new_name = "".join(["~", name[1:]])
return new_name
if isinstance(name, bytes):
if name.startswith(b" "):
new_name = b"".join([b"~", name[1:]])
return new_name
else:
if name.startswith(" "):
new_name = "".join(["~", name[1:]])
return new_name
return name
def _get_real_name(self, name):
"""Get real filepath of the file. See _map_name docstring for
details."""
for item in self._contents:
if item['display_name'] == name:
return item['fpath']
if isinstance(name, bytes):
if item[b'display_name'] == name.encode('utf-8',
'surrogateescape'):
return item[b'fpath']
else:
if item['display_name'] == name:
return item['fpath']
return None
def _get_dir(self):
@@ -67,7 +133,7 @@ class Archive(object):
if not out:
return
for line in out.split("\n"):
for line in out.split(b"\n"):
match = self.LINE_PAT.match(line)
if not match:
continue
@@ -93,8 +159,8 @@ class Archive(object):
command.append(src and src or dst)
try:
output = check_output(command)
except CalledProcessError:
output = subprocess.check_output(command)
except subprocess.CalledProcessError:
sys.exit(1)
return output
@@ -136,10 +202,65 @@ class Archive(object):
def usage():
"""Print out usage information"""
print ("Usage: %(prg)s {copyin,copyout} ARCHNAME SOURCE DESTINATION\n"
"or: %(prg)s list ARCHNAME\n"
"or: %(prg)s {mkdir,rm,rmdir,run} ARCHNAME TARGET" %
{"prg": sys.argv[0]})
print("Usage: %(prg)s {copyin,copyout} ARCHNAME SOURCE DESTINATION\n"
"or: %(prg)s list ARCHNAME\n"
"or: %(prg)s {mkdir,rm,rmdir,run} ARCHNAME TARGET" %
{"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="archive or 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="archive or 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):

View File

@@ -1,122 +0,0 @@
diff -ur unadf-0.7.11a.orig/Demo/unadf.c unadf-0.7.11a/Demo/unadf.c
--- unadf-0.7.11a.orig/Demo/unadf.c 2013-05-12 17:59:51.214905177 +0200
+++ unadf-0.7.11a/Demo/unadf.c 2013-05-12 17:50:06.843420519 +0200
@@ -58,6 +58,7 @@
puts(" -r : lists directory tree contents");
puts(" -c : use dircache data (must be used with -l)");
puts(" -s : display entries logical block pointer (must be used with -l)");
+ puts(" -m : display file comments, if exists (must be used with -l)");
putchar('\n');
puts(" -v n : mount volume #n instead of default #0 volume");
putchar('\n');
@@ -65,7 +66,8 @@
puts(" -d dir : extract to 'dir' directory");
}
-void printEnt(struct Volume *vol, struct Entry* entry, char *path, BOOL sect)
+void printEnt(struct Volume *vol, struct Entry* entry, char *path, BOOL sect,
+ BOOL comment)
{
/* do not print the links entries, ADFlib do not support them yet properly */
if (entry->type==ST_LFILE || entry->type==ST_LDIR || entry->type==ST_LSOFT)
@@ -89,7 +91,7 @@
printf("%s/",entry->name);
else
printf("%s",entry->name);
- if (entry->comment!=NULL && strlen(entry->comment)>0)
+ if (comment && entry->comment!=NULL && strlen(entry->comment)>0)
printf(", %s",entry->comment);
putchar('\n');
@@ -199,13 +201,14 @@
}
-void printTree(struct Volume *vol, struct List* tree, char* path, BOOL sect)
+void printTree(struct Volume *vol, struct List* tree, char* path, BOOL sect,
+ BOOL comment)
{
char *buf;
struct Entry* entry;
while(tree) {
- printEnt(vol, tree->content, path, sect);
+ printEnt(vol, tree->content, path, sect, comment);
if (tree->subdir!=NULL) {
entry = (struct Entry*)tree->content;
if (strlen(path)>0) {
@@ -215,11 +218,11 @@
return;
}
sprintf(buf,"%s/%s", path, entry->name);
- printTree(vol, tree->subdir, buf, sect);
+ printTree(vol, tree->subdir, buf, sect, comment);
free(buf);
}
else
- printTree(vol, tree->subdir, entry->name, sect);
+ printTree(vol, tree->subdir, entry->name, sect, comment);
}
tree = tree->next;
}
@@ -370,12 +373,10 @@
int main(int argc, char* argv[])
{
int i, j;
- BOOL rflag, lflag, xflag, cflag, vflag, sflag, dflag, pflag, qflag;
+ BOOL rflag, lflag, xflag, cflag, vflag, sflag, dflag, pflag, qflag, mflag;
struct List* files, *rtfiles;
char *devname, *dirname;
- char strbuf[80];
unsigned char *extbuf;
- int vInd, dInd, fInd, aInd;
BOOL nextArg;
struct Device *dev;
@@ -389,8 +390,7 @@
exit(0);
}
- rflag = lflag = cflag = vflag = sflag = dflag = pflag = qflag = FALSE;
- vInd = dInd = fInd = aInd = -1;
+ rflag = lflag = cflag = vflag = sflag = dflag = pflag = qflag = mflag = FALSE;
xflag = TRUE;
dirname = NULL;
devname = NULL;
@@ -430,6 +430,9 @@
case 's':
sflag = TRUE;
break;
+ case 'm':
+ mflag = TRUE;
+ break;
case 'c':
cflag = TRUE;
break;
@@ -522,13 +525,13 @@
if (!rflag) {
cell = list = adfGetDirEnt(vol,vol->curDirPtr);
while(cell) {
- printEnt(vol,cell->content,"", sflag);
+ printEnt(vol,cell->content,"", sflag, mflag);
cell = cell->next;
}
adfFreeDirList(list);
} else {
cell = list = adfGetRDirEnt(vol,vol->curDirPtr,TRUE);
- printTree(vol,cell,"", sflag);
+ printTree(vol,cell,"", sflag, mflag);
adfFreeDirList(list);
}
}else if (xflag) {
diff -ur unadf-0.7.11a.orig/Demo/unadf.usage unadf-0.7.11a/Demo/unadf.usage
--- unadf-0.7.11a.orig/Demo/unadf.usage 2006-12-03 15:27:00.000000000 +0100
+++ unadf-0.7.11a/Demo/unadf.usage 2013-05-12 17:40:23.116966854 +0200
@@ -3,6 +3,7 @@
-r : lists directory tree contents
-c : use dircache data (must be used with -l)
-s : display entries logical block pointer (must be used with -l)
+ -m : display file comments, if exists (must be used with -l)
-v n : mount volume #n instead of default #0 volume

26
setup.cfg Normal file
View File

@@ -0,0 +1,26 @@
[metadata]
author = Roman Dobosz
author_email = gryf73@gmail.com
classifiers =
Development Status :: 5 - Production/Stable
Environment :: Console
Intended Audience :: Developers
License :: OSI Approved :: BSD License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Topic :: Utilities
description = Midnight Commander extfslib helper library for writing extfs archive plugins.
license = BSD 3-Clause License
license_files = LICENSE
long_description = file: README.rst
name = extfslib
url = https://github.com/gryf/mc_extfslib
version = 1.4
[options]
py_modules = extfslib

4
setup.py Normal file
View File

@@ -0,0 +1,4 @@
import setuptools
setuptools.setup()

168
uadf
View File

@@ -1,168 +0,0 @@
#! /usr/bin/env python
"""
UADF Virtual filesystem
This extfs provides quick and dirty read-only access to disk image files for
the Commodore Amiga adf or adz (gzipped adfs) and dms.
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
source (and useful patches) in Debian repository:
http://packages.debian.org/sid/unadf
There should be one change made to the source of unadf, though. While using
"-lr" switch it by default also displays comments, separated by the comma.
However there is no way to distinguish where filename ends and comment starts,
if comment or filename already contains any comma.
It also requires xdms utility, for optional dms support.
Changelog:
1.1 Moved common code into extfslib library
1.0 Initial release
Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2013-05-12
Version: 1.1
Licence: BSD
"""
import sys
import re
import os
import gzip
from subprocess import check_output, check_call, CalledProcessError
from tempfile import mkstemp, mkdtemp
import shutil
from extfslib import Archive, parse_args
class UAdf(Archive):
"""
Class for interact with c1541 program and MC
"""
LINE_PAT = re.compile('\s*(?P<size>\d+)?'
'\s{2}(?P<date>\d{4}/\d{2}/\d{2})'
'\s{2}\s?(?P<time>\d+:\d{2}:\d{2})'
'\s{2}(?P<fpath>.*)')
ARCHIVER = "unadf"
DMS = "xdms"
CMDS = {"list": "-lr",
"read": "r",
"write": "w",
"delete": "d"}
DATETIME = "%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):
"""Prepare archive content for operations"""
self._clean = True
self._arch = fname
if fname.lower().endswith(".adz"):
self._ungzip()
if fname.lower().endswith(".dms"):
self._undms()
super(UAdf, self).__init__(self._arch)
def __del__(self):
"""Cleanup"""
if not self._clean:
try:
os.unlink(self._arch)
except OSError:
pass
def _parse_dt(self, date, time):
"""Return parsed datetime which fulfill extfs standards date."""
year, month, day = date.split("/")
hours, minutes, _unused = time.split(":")
return self.DATETIME % (month, day, year, int(hours), minutes)
def _ungzip(self):
"""Create temporary file for ungzipped adf file since unadf does not
accept gzipped content in any way including reading from stdin."""
fdesc, tmp_fname = mkstemp(suffix=".adf")
os.close(fdesc)
with gzip.open(self._arch) as gobj:
with open(tmp_fname, "wb") as fobj:
fobj.write(gobj.read())
self._arch = tmp_fname
self._clean = False
def _undms(self):
"""Create temporary adf file extracted from dms."""
fdesc, tmp_fname = mkstemp(suffix=".adf")
os.close(fdesc)
try:
check_call([self.DMS, '-q', 'u', self._arch, "+" + tmp_fname])
self._arch = tmp_fname
self._clean = False
except (CalledProcessError, OSError):
pass
def _get_dir(self):
"""Retrieve directory"""
contents = []
with open(os.devnull, "w") as fnull:
try:
out = check_output([self.ARCHIVER, self.CMDS['list'],
self._arch], stderr=fnull)
except CalledProcessError:
return contents
for line in out.split("\n"):
match = self.LINE_PAT.match(line)
if not match:
continue
entry = match.groupdict()
entry['perms'] = "-rw-r--r--"
if not entry['size']:
entry['perms'] = "drwxr-xr-x"
entry['size'] = "0"
entry['display_name'] = self._map_name(entry['fpath'])
entry['datetime'] = self._parse_dt(entry['date'], entry['time'])
entry['uid'] = self._uid
entry['gid'] = self._gid
contents.append(entry)
return contents
def list(self):
"""
Output list contents of adf image.
Convert filenames to be Unix filesystem friendly
Add suffix to show user what kind of file do he dealing with.
"""
for entry in self._contents:
sys.stdout.write(self.ITEM % entry)
return 0
def copyout(self, src, dst):
"""Copy file form the adf image."""
real_src = self._get_real_name(src)
if not real_src:
raise IOError("No such file or directory")
extract_dir = mkdtemp()
cmd = ["unadf", self._arch, real_src, "-d", extract_dir]
if check_call(cmd, stdout=open(os.devnull, 'wb'),
stderr=open(os.devnull, 'wb')) != 0:
shutil.rmtree(extract_dir)
sys.stderr.write("unadf returned with nonzero exit code\n")
return 1
shutil.move(os.path.join(extract_dir, real_src), dst)
shutil.rmtree(extract_dir)
return 0
if __name__ == "__main__":
sys.exit(parse_args(UAdf))

172
ulha
View File

@@ -1,172 +0,0 @@
#! /usr/bin/env python
"""
Lha Virtual filesystem executive for Midnight Commander.
Tested against python 2.7, lha[1] 1.14 and mc 4.8.7
[1] http://lha.sourceforge.jp
Changelog:
1.1 Moved common code into extfslib library
1.0 Initial release
Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2013-05-12
Version: 1.1
Licence: BSD
"""
import os
import sys
import re
import shutil
from subprocess import call, check_call, CalledProcessError
from tempfile import mkdtemp, mkstemp
from extfslib import Archive, parse_args
class ULha(Archive):
"""Archive handle. Provides interface to MC's extfs subsystem"""
LINE_PAT = re.compile("^((?P<perms>[d-][rswx-]{9})|(\[generic\])|"
"(\[unknown\]))"
"((\s+\d+/\d+\s+)|(\s+))"
"(?P<uid>)(?P<gid>)" # just for the record
"(?P<size>\d+)"
"\s+(\*{6}|\d+\.\d%)"
"\s(?P<month>[JFMASOND][a-z]{2})\s+" # month
"(?P<day>\d+)\s+" # day
"(?P<yh>\d{4}|(\d{2}:\d{2}))" # year/hour
"\s(?P<fpath>.*)")
ARCHIVER = "lha"
CMDS = {"list": "lq",
"read": "pq",
"write": "aq",
"delete": "dq"}
ITEM = ("%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s %(month)s %(day)s "
"%(yh)s %(display_name)s\n")
def _get_dir(self):
"""Prepare archive file listing"""
contents = []
out = self._call_command("list")
if not out:
return
for line in out.split("\n"):
# -lhd- can store empty directories
perms = "-rw-r--r--"
if line.endswith(os.path.sep):
line = line[:-1]
perms = "drw-r--r--"
match = self.LINE_PAT.match(line)
if not match:
continue
entry = match.groupdict()
# 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['uid'] = self._uid
entry['gid'] = self._gid
if not entry['perms']:
entry['perms'] = perms
entry['display_name'] = self._map_name(entry['fpath'])
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 % entry)
return 0
def rm(self, dst):
"""Remove file from archive"""
dst = self._get_real_name(dst)
# deleting with quiet option enabled will output nothing, so we get
# empty string here or None in case of error. Not smart.
if self._call_command('delete', dst=dst) is None:
return 1
return 0
def rmdir(self, dst):
"""Remove empty directory"""
dst = self._get_real_name(dst)
if not dst.endswith(os.path.sep):
dst += os.path.sep
if self._call_command('delete', dst=dst) is None:
return 1
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 mkdir(self, dst):
"""Create empty directory in archive"""
return self.copyin(dst)
def copyin(self, dst, src=None):
"""Copy file to the archive or create direcotry inside.
If src is empty, create empty directory with dst name."""
current_dir = os.path.abspath(os.curdir)
tmpdir = mkdtemp()
arch_abspath = os.path.realpath(self._arch)
os.chdir(tmpdir)
if src:
os.makedirs(os.path.dirname(dst))
os.link(src, dst)
else:
os.makedirs(dst)
try:
result = check_call([self.ARCHIVER, self.CMDS["write"],
arch_abspath, dst])
except CalledProcessError:
return 1
finally:
os.chdir(current_dir)
shutil.rmtree(tmpdir)
return result
def copyout(self, src, dst):
"""Copy file out form archive."""
src = self._get_real_name(src)
fobj = open(dst, "wb")
try:
result = check_call([self.ARCHIVER, self.CMDS['read'], self._arch,
src], stdout=fobj)
except CalledProcessError:
return 1
finally:
fobj.close()
return result
if __name__ == "__main__":
sys.exit(parse_args(ULha))

128
ulzx
View File

@@ -1,128 +0,0 @@
#! /usr/bin/env python
"""
Read only, Amiga LZX[1] archiver Virtual filesystem executive for Midnight
Commander.
Tested against python 2.7, unlzx[1] 1.1 and mc 4.8.7
[1] ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme
Changelog:
1.1 Moved common code into extfslib library
1.0 Initial release
Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2013-05-12
Version: 1.1
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("^\s+(?P<size>\d+)\s+"
"((n/a)|\d+)\s"
"(?P<time>\d{2}:\d{2}:\d{2})\s+"
"(?P<date>\d+-[a-z]{3}-\d{4})\s"
"(?P<perms>[h-][s-][p-][a-][r-][w-][e-][d-])\s"
"\"(?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", "feb", "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 = self._call_command("list")
if not out:
return
for line in out.split("\n"):
match = self.LINE_PAT.match(line)
if not match:
continue
entry = match.groupdict()
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'] = self._uid
entry['gid'] = 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 % 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:
shutil.copy2(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))