Compare commits

...

8 Commits

Author SHA1 Message Date
6b28a39f50 Removed _map_name method. 2023-10-23 18:16:54 +02:00
1a6c6af759 Cleanups. 2023-10-20 17:02:29 +02:00
cb5b03aa20 Readme update. 2023-10-18 16:43:32 +02:00
c78dbacd0e Use subprocess.run instaead oc check_call 2023-10-18 15:12:22 +02:00
c33655a12c Parse stderr for unadf banner.
Unadf does return 0 exit code always, even, if there was an issue in
either parsing image, mounting it or on any other issue. To be worst, it
always throw an banner to the stderr, so it cannot be used to check if
there was an error either.

As a workaround, let's check if there is a banner, strip it out and then
see if anything extra is on the stderr, so that it can be used to see if
there was any issues.
2023-10-18 15:08:00 +02:00
08a04c3862 Unify imports. 2023-10-18 15:06:44 +02:00
ed85f32837 Adapt to new version of unadf 1.2.
Adflib 0.8 has been released June 2023, so that this script needed to be
adapted to the new version.
2023-10-18 15:04:48 +02:00
09b2349152 Clean up imports 2023-10-17 23:01:42 +02:00
2 changed files with 108 additions and 101 deletions

View File

@@ -18,64 +18,50 @@ In case of corrupted or no-dos images, message will be shown.
Requirements Requirements
------------ ------------
It requires ``unadf`` utility from `ADFlib <https://github.com/lclevy/ADFlib>`_ This script is using ``unadf`` v1.2 utility from `ADFlib
repository. <https://github.com/lclevy/ADFlib>`_ package in version 0.8. Version of unadf
can be check by simply issuing unadf without arguments:
If it turns out that your distribution doesn't provide proper version of ADFlib, .. code:: shell-session
there will be a need for building it by hand.
$ unadf
If it turns out that your distribution doesn't provide proper version of
ADFlib, there will be a need for building it by hand.
It may be done by using following steps: It may be done by using following steps:
#. Grab the `sources #. Grab the `sources <https://github.com/lclevy/ADFlib>`_
<http://http.debian.net/debian/pool/main/u/unadf/unadf_0.7.11a.orig.tar.gz>`_ #. Build and install it, using instructions from `INSTALL
and `patches <https://github.com/lclevy/ADFlib/blob/master/INSTALL>`_ file.
<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 For optional dms support, `xdms <http://zakalwe.fi/~shd/foss/xdms/>`_ utility
$ cd temp is needed.
$ 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::
$ for i in `cat ../debian/patches/series`; do
> patch -Np1 < "../debian/patches/${i}"
> done
#. 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
needed.
Installation Installation
------------ ------------
* install `extfslib`_ * install `extfslib`_
* copy ``uadf`` to ``~/.local/share/mc/extfs.d/`` * copy ``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.ini``:
# adf .. code:: ini
type/^Amiga\ .* disk
Open=%cd %p/uadf://
View=%view{ascii} unadf -lr %f
# adz [adf]
regex/\.([aA][dD][zZ])$ Type=^Amiga\ .* disk
Open=%cd %p/uadf:// Open=%cd %p/uadf://
View=%view{ascii} unadf -lrm %f 2>/dev/null
[adz]
Regex=\.adz$
View=%view{ascii} t=$(mktemp --suffix .adf); zcat %f > ${t}; unadf -lrm ${t} 2</dev/null; rm ${t}
Open=%cd %p/uadf://
[dms]
Regex=\.dms$
View=%view{ascii} t=$(mktemp --suffix .adf); xdms u %f "+${t}" 2>/dev/null; unadf -lrm ${t} 2</dev/null; rm ${t}
Open=%cd %p/uadf://
# dms
regex/\.([dD][mM][sS])$
Open=%cd %p/uadf://
License License
======= =======

135
uadf
View File

@@ -20,43 +20,48 @@ 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.4 Adapt to unadf 1.2 and use Latin1 as default encoding
1.3 Switch to Python3 1.3 Switch to Python3
1.2 Added failsafe for filenames in archive with spaces and nodos message. 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: 2019-06-30 Date: 2023-10-19
Version: 1.3 Version: 1.4
Licence: BSD Licence: BSD
""" """
import sys
import re
import os
import gzip import gzip
from subprocess import check_output, check_call, CalledProcessError import os
from tempfile import mkstemp, mkdtemp import re
import shutil import shutil
import subprocess
import sys
import tempfile
from extfslib import Archive, parse_args import extfslib
class UAdf(Archive): BANNER_PAT = re.compile(r'unADF v\d.\d : a unzip like for .ADF files, '
r'powered by ADFlib (.*)\n\n')
class UAdf(extfslib.Archive):
""" """
Class for interact with c1541 program and MC Class for interact with ADF/DMS images and MC
""" """
LINE_PAT = re.compile(b'\s*(?P<size>\d+)?' LINE_PAT = re.compile(r'\s*(?P<size>\d+)?'
b'\s{2}(?P<date>\d{4}/\d{2}/\d{2})' r'\s{2}(?P<date>\d{4}/\d{2}/\d{2})'
b'\s{2}\s?(?P<time>\d+:\d{2}:\d{2})' r'\s{2}\s?(?P<time>\d+:\d{2}:\d{2})'
b'\s{2}(?P<fpath>.*)') r'\s{2}(?P<fpath>.*)')
ARCHIVER = b"unadf" ARCHIVER = "unadf"
DMS = b"xdms" DMS = "xdms"
CMDS = {"list": b"-lr", CMDS = {"list": "-lr",
"read": b"r", "read": "r",
"write": b"w", "write": "w",
"delete": b"d"} "delete": "d"}
DATETIME = b"%s-%s-%s %02d:%s" DATETIME = "%s-%s-%s %02d:%s"
def __init__(self, fname): def __init__(self, fname):
"""Prepare archive content for operations""" """Prepare archive content for operations"""
@@ -81,14 +86,14 @@ 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(b"/") year, month, day = date.split("/")
hours, minutes, _unused = time.split(b":") hours, minutes, _unused = time.split(":")
return self.DATETIME % (month, day, year, int(hours), minutes) return self.DATETIME % (month, day, year, int(hours), minutes)
def _ungzip(self): def _ungzip(self):
"""Create temporary file for ungzipped adf file since unadf does not """Create temporary file for ungzipped adf file since unadf does not
accept gzipped content in any way including reading from stdin.""" accept gzipped content in any way including reading from stdin."""
fdesc, tmp_fname = mkstemp(suffix=".adf") fdesc, tmp_fname = tempfile.mkstemp(suffix=".adf")
os.close(fdesc) os.close(fdesc)
with gzip.open(self._arch) as gobj: with gzip.open(self._arch) as gobj:
@@ -99,27 +104,36 @@ class UAdf(Archive):
def _undms(self): def _undms(self):
"""Create temporary adf file extracted from dms.""" """Create temporary adf file extracted from dms."""
fdesc, tmp_fname = mkstemp(suffix=".adf") fdesc, tmp_fname = tempfile.mkstemp(suffix=".adf")
os.close(fdesc) os.close(fdesc)
try: result = subprocess.run([self.DMS, '-q', 'u', self._arch,
check_call([self.DMS, b'-q', b'u', self._arch, "+" + tmp_fname]) "+" + tmp_fname])
if result.returncode == 0:
self._arch = tmp_fname self._arch = tmp_fname
self._clean = False self._clean = False
except (CalledProcessError, OSError):
pass def _parse_banner(self, string):
match = BANNER_PAT.match(string)
if not match:
return
if match.end() == len(string):
return
return string[match.end():]
def _get_dir(self): def _get_dir(self):
"""Retrieve directory""" """Retrieve directory"""
contents = [] contents = []
with open(os.devnull, "w") as fnull: out = subprocess.run([self.ARCHIVER, self.CMDS['list'], self._arch],
try: capture_output=True, encoding="latin-1")
out = check_output([self.ARCHIVER, self.CMDS['list'],
self._arch], stderr=fnull)
except CalledProcessError:
return contents
for line in out.split(b"\n"): error_msg = self._parse_banner(out.stderr)
if error_msg:
sys.stderr.write(error_msg)
for line in out.stdout.split("\n"):
match = self.LINE_PAT.match(line) match = self.LINE_PAT.match(line)
if not match: if not match:
continue continue
@@ -127,17 +141,17 @@ class UAdf(Archive):
match_entry = match.groupdict() match_entry = match.groupdict()
entry = {} entry = {}
for key in match_entry: for key in match_entry:
entry[bytes(key, 'utf-8')] = match_entry[key] entry[key] = match_entry[key]
del match_entry del match_entry
entry[b'perms'] = b"-rw-r--r--" entry['perms'] = "-rw-r--r--"
if not entry[b'size']: if not entry['size']:
entry[b'perms'] = b"drwxr-xr-x" entry['perms'] = "drwxr-xr-x"
entry[b'size'] = b"0" entry['size'] = "0"
entry[b'display_name'] = self._map_name(entry[b'fpath']) entry['display_name'] = self._map_name(entry['fpath'])
entry[b'datetime'] = self._parse_dt(entry[b'date'], entry[b'time']) entry['datetime'] = self._parse_dt(entry['date'], entry['time'])
entry[b'uid'] = bytes(str(self._uid), 'utf-8') entry['uid'] = str(self._uid)
entry[b'gid'] = bytes(str(self._gid), 'utf-8') entry['gid'] = str(self._gid)
contents.append(entry) contents.append(entry)
return contents return contents
@@ -149,39 +163,46 @@ class UAdf(Archive):
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: if not self._contents:
sys.stderr.write("Nodos or archive error\n") sys.stderr.write("Nodos or image error\n")
return 1 return 1
for entry in self._contents: for entry in self._contents:
sys.stdout.buffer.write(self.ITEM % entry) print(self.ITEM.decode('utf-8')[:-1] % entry)
return 0 return 0
def copyout(self, src, dst): def copyout(self, src, dst):
"""Copy file form the adf image.""" """Copy file form the adf image."""
real_src = self._get_real_name(src) real_src = [e['display_name'] for e in self._contents
if e['display_name'] == src]
if not real_src: if not real_src:
raise IOError("No such file or directory") raise IOError("No such file or directory")
real_src = real_src[0].encode('latin-1')
if b" " in real_src: if b" " in real_src:
sys.stderr.write("unadf is unable to operate on filepath with " sys.stderr.write("unadf is unable to operate on filepath with "
"space inside.\nUse affs to mount image and than" "space inside.\nUse affs to mount image and than"
" extract desired files.\n") " extract desired files.\n")
return 1 return 1
extract_dir = mkdtemp() extract_dir = tempfile.mkdtemp()
cmd = [self.ARCHIVER, self._arch, real_src, b"-d", extract_dir] cmd = [self.ARCHIVER, "-d", extract_dir, self._arch, real_src]
if check_call(cmd, stdout=open(os.devnull, 'wb'), result = subprocess.run(cmd, capture_output=True)
stderr=open(os.devnull, 'wb')) != 0:
error_msg = self._parse_banner(result.stderr.decode('utf-8'))
if error_msg:
sys.stderr.write("unadf returned with error:\n")
sys.stderr.write(error_msg)
shutil.rmtree(extract_dir) shutil.rmtree(extract_dir)
sys.stderr.write("unadf returned with nonzero exit code\n")
return 1 return 1
shutil.move(os.path.join(bytes(extract_dir, "utf8"), real_src), # use subprocess, as shutil will crash on binary encoded filenames
bytes(dst, "utf8")) subprocess.run([b'mv', os.path.join(extract_dir.encode("latin-1"),
real_src), dst.encode('latin-1')])
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(extfslib.parse_args(UAdf))