Renamed repository. Added common lib for all extfs plugins.
Added uadf and ulzx.
This commit is contained in:
127
README.rst
127
README.rst
@@ -1,16 +1,129 @@
|
|||||||
=================================
|
========================
|
||||||
ulha extfs for Midnight Commander
|
Midnight Commander extfs
|
||||||
=================================
|
========================
|
||||||
|
|
||||||
This is Midnight Commander extfs plugin for handling lha/lzh archives.
|
Those are Midnight Commander extfs plugins for handling several archive types.
|
||||||
It requires `lha <http://lha.sourceforge.jp>`_ free LHA implementation to work.
|
|
||||||
|
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``::
|
||||||
|
|
||||||
|
# arch
|
||||||
|
regex/\.pattern$
|
||||||
|
Open=%cd %p/handler_filename://
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
ULha requires `free lha <http://lha.sourceforge.jp>`_ implementation to work.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
* copy ``ulha.py`` to ``~/.local/share/mc/extfs/ulha``
|
* copy ``extfslib.py`` and ``ulha`` to ``~/.local/share/mc/extfs/``
|
||||||
* add or change entry for lha/lzh files handle in ``~/.config/mc/mc.ext``::
|
* add or change entry for files handle in ``~/.config/mc/mc.ext``::
|
||||||
|
|
||||||
# lha
|
# lha
|
||||||
regex/\.[lL]([Hh][aA]|[Zz][hH])$
|
regex/\.[lL]([Hh][aA]|[Zz][hH])$
|
||||||
Open=%cd %p/ulha://
|
Open=%cd %p/ulha://
|
||||||
View=%view{ascii} lha l %f
|
View=%view{ascii} lha l %f
|
||||||
|
|
||||||
|
ULzx
|
||||||
|
====
|
||||||
|
ULzx is an extfs plugin which can be used to browse and extract lzx archives,
|
||||||
|
which are known almost exclusively from Amiga.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
ULzx requires
|
||||||
|
`unlzx <ftp://us.aminet.net/pub/aminet/misc/unix/unlzx.c.gz.readme>`_ tool.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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://
|
||||||
|
|||||||
177
extfslib.py
Normal file
177
extfslib.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
Changelog:
|
||||||
|
1.0 Initial release
|
||||||
|
|
||||||
|
Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
|
||||||
|
Date: 2013-05-12
|
||||||
|
Version: 1.0
|
||||||
|
Licence: BSD
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
from subprocess import check_output, CalledProcessError
|
||||||
|
|
||||||
|
|
||||||
|
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"}
|
||||||
|
|
||||||
|
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._arch = fname
|
||||||
|
self._contents = self._get_dir()
|
||||||
|
|
||||||
|
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
|
||||||
|
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']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_dir(self):
|
||||||
|
"""Prepare archive file listing. Expected keys which every entry
|
||||||
|
should have are: size, perms, uid, gid, date, time, fpath and
|
||||||
|
display_name."""
|
||||||
|
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()
|
||||||
|
contents.append(entry)
|
||||||
|
|
||||||
|
return contents
|
||||||
|
|
||||||
|
def _call_command(self, cmd, src=None, dst=None):
|
||||||
|
"""
|
||||||
|
Return status of the provided command, which can be one of:
|
||||||
|
write
|
||||||
|
read
|
||||||
|
delete
|
||||||
|
list
|
||||||
|
"""
|
||||||
|
command = [self.ARCHIVER, self.CMDS.get(cmd), self._arch]
|
||||||
|
|
||||||
|
if src and dst:
|
||||||
|
command.append(src)
|
||||||
|
command.append(dst)
|
||||||
|
elif src or dst:
|
||||||
|
command.append(src and src or dst)
|
||||||
|
|
||||||
|
try:
|
||||||
|
output = check_output(command)
|
||||||
|
except CalledProcessError:
|
||||||
|
sys.exit(1)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
"""Output contents of the archive to stdout"""
|
||||||
|
sys.stderr.write("Not supported")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def run(self, dst):
|
||||||
|
"""Execute file out of archive"""
|
||||||
|
sys.stderr.write("Not supported")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def copyout(self, src, dst):
|
||||||
|
"""Copy file out of archive"""
|
||||||
|
sys.stderr.write("Not supported")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def rm(self, dst):
|
||||||
|
"""Remove file from archive"""
|
||||||
|
sys.stderr.write("Not supported")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def mkdir(self, dst):
|
||||||
|
"""Create empty directory in archive"""
|
||||||
|
sys.stderr.write("Not supported")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def rmdir(self, dst):
|
||||||
|
"""Removes directory from archive"""
|
||||||
|
sys.stderr.write("Not supported")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def copyin(self, dst, src=None):
|
||||||
|
"""Copy file to the archive"""
|
||||||
|
sys.stderr.write("Not supported")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
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]})
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(arch_class):
|
||||||
|
"""Retrive and parse arguments from commandline and apply them into passed
|
||||||
|
arch_class class object."""
|
||||||
|
try:
|
||||||
|
if sys.argv[1] not in ('list', 'copyin', 'copyout', 'rm', 'mkdir',
|
||||||
|
"run", "rmdir"):
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
except IndexError:
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
arch = src = dst = None
|
||||||
|
try:
|
||||||
|
arch = sys.argv[2]
|
||||||
|
if sys.argv[1] in ('copyin', 'copyout'):
|
||||||
|
src = sys.argv[3]
|
||||||
|
dst = sys.argv[4]
|
||||||
|
elif sys.argv[1] in ('rm', 'rmdir', 'run', 'mkdir'):
|
||||||
|
dst = sys.argv[3]
|
||||||
|
except IndexError:
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
call_map = {'copyin': lambda a, s, d: arch_class(a).copyin(s, d),
|
||||||
|
'copyout': lambda a, s, d: arch_class(a).copyout(s, d),
|
||||||
|
'list': lambda a, s, d: arch_class(a).list(),
|
||||||
|
'mkdir': lambda a, s, d: arch_class(a).mkdir(d),
|
||||||
|
'rm': lambda a, s, d: arch_class(a).rm(d),
|
||||||
|
'rmdir': lambda a, s, d: arch_class(a).rmdir(d),
|
||||||
|
'run': lambda a, s, d: arch_class(a).run(d)}
|
||||||
|
|
||||||
|
return call_map[sys.argv[1]](arch, src, dst)
|
||||||
122
extras/unadf_separate_comment.patch
Normal file
122
extras/unadf_separate_comment.patch
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
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
|
||||||
|
|
||||||
168
uadf
Executable file
168
uadf
Executable file
@@ -0,0 +1,168 @@
|
|||||||
|
#! /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))
|
||||||
247
ulha
247
ulha
@@ -7,113 +7,61 @@ Tested against python 2.7, lha[1] 1.14 and mc 4.8.7
|
|||||||
[1] http://lha.sourceforge.jp
|
[1] http://lha.sourceforge.jp
|
||||||
|
|
||||||
Changelog:
|
Changelog:
|
||||||
|
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-05
|
Date: 2013-05-12
|
||||||
Version: 1.0
|
Version: 1.1
|
||||||
Licence: BSD
|
Licence: BSD
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
from subprocess import call, check_call, check_output, CalledProcessError
|
from subprocess import call, check_call, CalledProcessError
|
||||||
from tempfile import mkdtemp, mkstemp
|
from tempfile import mkdtemp, mkstemp
|
||||||
|
|
||||||
|
from extfslib import Archive, parse_args
|
||||||
# Define which archiver you are using with appropriate options
|
|
||||||
ARCHIVER = "lha"
|
|
||||||
CMDS = {"list": "lq",
|
|
||||||
"read": "pq",
|
|
||||||
"write": "aq",
|
|
||||||
"delete": "dq"}
|
|
||||||
|
|
||||||
LINE_LHD = re.compile("^(?P<perms>[d-][rswx-]{9})"
|
|
||||||
"\s+(?P<uid>\d+)/"
|
|
||||||
"(?P<gid>\d+)"
|
|
||||||
"\s+(?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>.*)")
|
|
||||||
|
|
||||||
LINE_LHx = re.compile("^(?P<perms>(\[generic\])|(\[unknown\])|([d-][rswx-]{9}))"
|
|
||||||
"\s+(?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>.*)")
|
|
||||||
|
|
||||||
|
|
||||||
class Archive(object):
|
class ULha(Archive):
|
||||||
"""Archive handle. Provides interface to MC's extfs subsystem"""
|
"""Archive handle. Provides interface to MC's extfs subsystem"""
|
||||||
def __init__(self, fname):
|
|
||||||
"""Prepare archive content for operations"""
|
|
||||||
self._filemap = {}
|
|
||||||
self._arch = fname
|
|
||||||
self._pattern = None
|
|
||||||
self._uid = str(os.getuid())
|
|
||||||
self._gid = str(os.getgid())
|
|
||||||
|
|
||||||
self._contents = self._get_dir()
|
LINE_PAT = re.compile("^((?P<perms>[d-][rswx-]{9})|(\[generic\])|"
|
||||||
|
"(\[unknown\]))"
|
||||||
def _identify(self):
|
"((\s+\d+/\d+\s+)|(\s+))"
|
||||||
"""Check for lha header"""
|
"(?P<uid>)(?P<gid>)" # just for the record
|
||||||
pat_map = {"-lhd-": LINE_LHD,
|
"(?P<size>\d+)"
|
||||||
"-lh0-": LINE_LHx,
|
"\s+(\*{6}|\d+\.\d%)"
|
||||||
"-lh1-": LINE_LHx,
|
"\s(?P<month>[JFMASOND][a-z]{2})\s+" # month
|
||||||
"-lh5-": LINE_LHx,
|
"(?P<day>\d+)\s+" # day
|
||||||
"-lh6-": LINE_LHx}
|
"(?P<yh>\d{4}|(\d{2}:\d{2}))" # year/hour
|
||||||
fobj = open(self._arch)
|
"\s(?P<fpath>.*)")
|
||||||
fobj.seek(2)
|
ARCHIVER = "lha"
|
||||||
ident = fobj.read(5)
|
CMDS = {"list": "lq",
|
||||||
fobj.close()
|
"read": "pq",
|
||||||
return pat_map[ident]
|
"write": "aq",
|
||||||
|
"delete": "dq"}
|
||||||
def _map_name(self, name):
|
ITEM = ("%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s %(month)s %(day)s "
|
||||||
"""MC still have a bug in extfs subsystem, in case of filepaths with
|
"%(yh)s %(display_name)s\n")
|
||||||
leading space. This is workaround to this bug, which replaces leading
|
|
||||||
space with tilda. Real name is remembered in _filemap attribute and
|
|
||||||
used in real operations."""
|
|
||||||
if name.startswith(" "):
|
|
||||||
new_name = "".join(["~", name[1:]])
|
|
||||||
self._filemap[new_name] = name
|
|
||||||
return new_name
|
|
||||||
return name
|
|
||||||
|
|
||||||
def _get_real_name(self, name):
|
|
||||||
"""Get real filepath of the file. See _map_name docstring for
|
|
||||||
details."""
|
|
||||||
new_name = self._filemap.get(name)
|
|
||||||
if new_name:
|
|
||||||
return new_name
|
|
||||||
return name
|
|
||||||
|
|
||||||
def _get_dir(self):
|
def _get_dir(self):
|
||||||
"""Prepare archive file listing"""
|
"""Prepare archive file listing"""
|
||||||
if not self._pattern:
|
|
||||||
self._pattern = self._identify()
|
|
||||||
|
|
||||||
self._filemap = {}
|
|
||||||
contents = []
|
contents = []
|
||||||
|
|
||||||
if self._pattern == LINE_LHx:
|
|
||||||
perms = "-rw-r--r--"
|
|
||||||
|
|
||||||
out = self._call_command("list")
|
out = self._call_command("list")
|
||||||
if not out:
|
if not out:
|
||||||
return
|
return
|
||||||
|
|
||||||
for line in out.split("\n"):
|
for line in out.split("\n"):
|
||||||
if line.endswith("/"):
|
# -lhd- can store empty directories
|
||||||
|
perms = "-rw-r--r--"
|
||||||
|
if line.endswith(os.path.sep):
|
||||||
line = line[:-1]
|
line = line[:-1]
|
||||||
if self._pattern == LINE_LHx:
|
perms = "drw-r--r--"
|
||||||
perms = "drw-r--r--"
|
|
||||||
|
|
||||||
match = self._pattern.match(line)
|
match = self.LINE_PAT.match(line)
|
||||||
if not match:
|
if not match:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -125,42 +73,17 @@ class Archive(object):
|
|||||||
entry['uid'] = self._uid
|
entry['uid'] = self._uid
|
||||||
entry['gid'] = self._gid
|
entry['gid'] = self._gid
|
||||||
|
|
||||||
if self._pattern == LINE_LHx:
|
if not entry['perms']:
|
||||||
entry['perms'] = perms
|
entry['perms'] = perms
|
||||||
|
|
||||||
entry['display_name'] = self._map_name(entry['fpath'])
|
entry['display_name'] = self._map_name(entry['fpath'])
|
||||||
contents.append(entry)
|
contents.append(entry)
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
def _call_command(self, cmd, src=None, dst=None):
|
|
||||||
"""
|
|
||||||
Return status of the provided command, which can be one of:
|
|
||||||
write
|
|
||||||
read
|
|
||||||
delete
|
|
||||||
list
|
|
||||||
"""
|
|
||||||
command = [ARCHIVER, CMDS.get(cmd), self._arch]
|
|
||||||
|
|
||||||
if src and dst:
|
|
||||||
command.append(src)
|
|
||||||
command.append(dst)
|
|
||||||
elif src or dst:
|
|
||||||
command.append(src and src or dst)
|
|
||||||
|
|
||||||
try:
|
|
||||||
output = check_output(command)
|
|
||||||
except CalledProcessError:
|
|
||||||
return None
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
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("%(perms)s 1 %(uid)-8s %(gid)-8s %(size)8s "
|
sys.stdout.write(self.ITEM % entry)
|
||||||
"%(month)s %(day)s %(yh)s %(display_name)s\n" %
|
|
||||||
entry)
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def rm(self, dst):
|
def rm(self, dst):
|
||||||
@@ -172,6 +95,16 @@ class Archive(object):
|
|||||||
return 1
|
return 1
|
||||||
return 0
|
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):
|
def run(self, dst):
|
||||||
"""Execute file out of archive"""
|
"""Execute file out of archive"""
|
||||||
fdesc, tmp_file = mkstemp()
|
fdesc, tmp_file = mkstemp()
|
||||||
@@ -212,7 +145,8 @@ class Archive(object):
|
|||||||
os.makedirs(dst)
|
os.makedirs(dst)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = check_call([ARCHIVER, CMDS["write"], arch_abspath, dst])
|
result = check_call([self.ARCHIVER, self.CMDS["write"],
|
||||||
|
arch_abspath, dst])
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
return 1
|
return 1
|
||||||
finally:
|
finally:
|
||||||
@@ -225,105 +159,14 @@ class Archive(object):
|
|||||||
src = self._get_real_name(src)
|
src = self._get_real_name(src)
|
||||||
fobj = open(dst, "wb")
|
fobj = open(dst, "wb")
|
||||||
try:
|
try:
|
||||||
result = check_call([ARCHIVER, CMDS['read'], self._arch, src],
|
result = check_call([self.ARCHIVER, self.CMDS['read'], self._arch,
|
||||||
stdout=fobj)
|
src], stdout=fobj)
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
return 1
|
return 1
|
||||||
finally:
|
finally:
|
||||||
fobj.close()
|
fobj.close()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
CALL_MAP = {'list': lambda a: Archive(a.arch).list(),
|
|
||||||
'copyin': lambda a: Archive(a.arch).copyin(a.src, a.dst),
|
|
||||||
'copyout': lambda a: Archive(a.arch).copyout(a.src, a.dst),
|
|
||||||
'mkdir': lambda a: Archive(a.arch).mkdir(a.dst),
|
|
||||||
'rm': lambda a: Archive(a.arch).rm(a.dst),
|
|
||||||
'run': lambda a: Archive(a.arch).run(a.dst)}
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
"""Use ArgumentParser to check for script arguments and execute."""
|
|
||||||
parser = 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 in 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 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 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 no_parse():
|
|
||||||
"""Failsafe argument "parsing". Note, that it blindly takes positional
|
|
||||||
arguments without checking them. In case of wrong arguments it will
|
|
||||||
silently exit"""
|
|
||||||
try:
|
|
||||||
if sys.argv[1] not in ('list', 'copyin', 'copyout', 'rm', 'mkdir',
|
|
||||||
"run"):
|
|
||||||
sys.exit(2)
|
|
||||||
except IndexError:
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
class Arg(object):
|
|
||||||
"""Mimic argparse object"""
|
|
||||||
dst = None
|
|
||||||
src = None
|
|
||||||
arch = None
|
|
||||||
|
|
||||||
arg = Arg()
|
|
||||||
|
|
||||||
try:
|
|
||||||
arg.arch = sys.argv[2]
|
|
||||||
if sys.argv[1] in ('copyin', 'copyout'):
|
|
||||||
arg.src = sys.argv[3]
|
|
||||||
arg.dst = sys.argv[4]
|
|
||||||
elif sys.argv[1] in ('rm', 'run', 'mkdir'):
|
|
||||||
arg.dst = sys.argv[3]
|
|
||||||
except IndexError:
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
return CALL_MAP[sys.argv[1]](arg)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
sys.exit(parse_args(ULha))
|
||||||
from argparse import ArgumentParser
|
|
||||||
PARSE_FUNC = parse_args
|
|
||||||
except ImportError:
|
|
||||||
PARSE_FUNC = no_parse
|
|
||||||
|
|
||||||
sys.exit(PARSE_FUNC())
|
|
||||||
|
|||||||
128
ulzx
Executable file
128
ulzx
Executable file
@@ -0,0 +1,128 @@
|
|||||||
|
#! /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))
|
||||||
Reference in New Issue
Block a user