1
0
mirror of https://github.com/gryf/uc1541.git synced 2026-02-03 12:15:47 +01:00

2 Commits
v3.3 ... v3.5

Author SHA1 Message Date
6f359f7191 Drop Python2 support.
Starting from beginning of 2020 Python 2.7 is no longer supported[1],
hence the change of shebang for uc1541 script and removal of _ord
function.

[1] https://devguide.python.org/versions/
2023-10-04 15:23:27 +02:00
60dfd1f687 Code cleanup. 2020-06-28 11:56:00 +02:00
2 changed files with 45 additions and 122 deletions

View File

@@ -1,5 +1,5 @@
=================================================== ===================================================
Midnight Commander virtual filesystem for d64 files Midnight Commander virtual filesystem for C64 disks
=================================================== ===================================================
This extfs provides an access to disk image files for the Commodore This extfs provides an access to disk image files for the Commodore
@@ -10,10 +10,10 @@ made by Commodore.
Features Features
======== ========
* view, remove, copy files in and out of the image * View, remove, copy files in and out of the image,
* support for read from and write to d64, d71 and d81 images * Support for read from and write to d64, d71 and d81 images,
* gzipped disk images support * Gzipped disk images support,
* onfly characters remapping (see below) * On fly characters remapping (see below).
Remarks Remarks
======= =======
@@ -33,12 +33,12 @@ image. Following rules was applied to represent a single file entry:
While copying from disk image to filesystem, filenames will be stored as they While copying from disk image to filesystem, filenames will be stored as they
are seen on a listing. are seen on a listing.
While copying from filesystem to D64 image, filename conversion will be done: While copying from filesystem to disk image, filename conversion will be done:
1. Every ``$`` and ``*`` characters will be replaced by question mark (``?``) 1. Every ``$`` and ``*`` characters will be replaced by question mark (``?``)
2. Every pipe (``|``) and backslash (``\``) characters will be replaced by 2. Every pipe (``|``) and backslash (``\``) characters will be replaced by
slash (``/``) slash (``/``)
3. Every tilda (``~``) will be replaced by a space 3. Every tilde (``~``) will be replaced by a space
4. ``prg`` extension will be truncated 4. ``prg`` extension will be truncated
Representation of a directory can be sometimes confusing - in case when one Representation of a directory can be sometimes confusing - in case when one
@@ -57,7 +57,7 @@ file.
Rquirements Rquirements
=========== ===========
* Python 2.7 or Python 3.6 or higher * Python 3.6 or higher (checked recently with 3.11)
* Vice installation (c1541 program in path) * Vice installation (c1541 program in path)
Installation Installation
@@ -87,6 +87,9 @@ script behaviour:
Changelog Changelog
========= =========
* **3.5** Drop Python2 support.
* **3.4** Code cleanup. Removed dummy logger class and sys.args based argument
parsing.
* **3.3** Added support for .d71 and .d81 disk images. * **3.3** Added support for .d71 and .d81 disk images.
* **3.2** Changed shebang to ``python`` executable instead of ``python3`` * **3.2** Changed shebang to ``python`` executable instead of ``python3``
* **3.1** Argparse on Python3 have different behaviour, where if no subcommand * **3.1** Argparse on Python3 have different behaviour, where if no subcommand

146
uc1541
View File

@@ -1,24 +1,25 @@
#!/usr/bin/env python #!/usr/bin/env python3
""" """
UC1541 Virtual filesystem UC1541 Virtual filesystem
Author: Roman 'gryf' Dobosz <gryf73@gmail.com> Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2019-09-20 Date: 2023-10-04
Version: 3.3 Version: 3.5
Licence: BSD Licence: BSD
source: https://bitbucket.org/gryf/uc1541 source: https://bitbucket.org/gryf/uc1541
mirror: https://github.com/gryf/uc1541 mirror: https://github.com/gryf/uc1541
""" """
import argparse
import sys
import re
import os
import gzip import gzip
from subprocess import Popen, PIPE
if os.getenv('UC1541_DEBUG'):
import logging import logging
import os
import re
import subprocess
import sys
LOG = logging.getLogger('UC1541') LOG = logging.getLogger('UC1541')
if os.getenv('UC1541_DEBUG'):
LOG.setLevel(logging.DEBUG) LOG.setLevel(logging.DEBUG)
FILE_HANDLER = logging.FileHandler("/tmp/uc1541.log") FILE_HANDLER = logging.FileHandler("/tmp/uc1541.log")
FILE_FORMATTER = logging.Formatter("%(asctime)s %(levelname)-8s " FILE_FORMATTER = logging.Formatter("%(asctime)s %(levelname)-8s "
@@ -26,47 +27,11 @@ if os.getenv('UC1541_DEBUG'):
FILE_HANDLER.setFormatter(FILE_FORMATTER) FILE_HANDLER.setFormatter(FILE_FORMATTER)
FILE_HANDLER.setLevel(logging.DEBUG) FILE_HANDLER.setLevel(logging.DEBUG)
LOG.addHandler(FILE_HANDLER) LOG.addHandler(FILE_HANDLER)
else:
class LOG(object):
"""
Dummy logger object. Does nothing.
"""
@classmethod
def debug(*args, **kwargs):
pass
@classmethod
def info(*args, **kwargs):
pass
@classmethod
def warning(*args, **kwargs):
pass
@classmethod
def error(*args, **kwargs):
pass
@classmethod
def critical(*args, **kwargs):
pass
SECLEN = 256 SECLEN = 256
def _ord(string_or_int):
"""
Return an int value for the (possible) string passed in argument. This
function is for compatibility between python2 and python3, where single
element in byte string array is a string or an int respectively.
"""
try:
return ord(string_or_int)
except TypeError:
return string_or_int
def _get_raw(dimage): def _get_raw(dimage):
""" """
Try to get contents of the D64 image either it's gzip compressed or not. Try to get contents of the D64 image either it's gzip compressed or not.
@@ -160,10 +125,10 @@ class Disk(object):
filename = list() filename = list()
for chr_ in string: for chr_ in string:
if _ord(chr_) == 160: # shift+space character; $a0 if chr_ == 160: # shift+space character; $a0
break break
character = D64.CHAR_MAP.get(_ord(chr_), '?') character = D64.CHAR_MAP.get(chr_, '?')
filename.append(character) filename.append(character)
# special cases # special cases
@@ -204,8 +169,8 @@ class Disk(object):
if not self.current_sector_data: if not self.current_sector_data:
return False return False
self.next_track = _ord(self.current_sector_data[0]) self.next_track = self.current_sector_data[0]
self.next_sector = _ord(self.current_sector_data[1]) self.next_sector = self.current_sector_data[1]
if (self.next_track, self.next_sector) in self._already_done: if (self.next_track, self.next_sector) in self._already_done:
# Just a failsafe. Endless loop is not what is expected. # Just a failsafe. Endless loop is not what is expected.
@@ -239,7 +204,7 @@ class Disk(object):
sector = self.current_sector_data sector = self.current_sector_data
for dummy in range(8): for dummy in range(8):
entry = sector[:32] entry = sector[:32]
ftype = _ord(entry[2]) ftype = entry[2]
if ftype == 0: # deleted if ftype == 0: # deleted
sector = sector[32:] sector = sector[32:]
@@ -247,12 +212,12 @@ class Disk(object):
type_verbose = self._get_ftype(ftype) type_verbose = self._get_ftype(ftype)
protect = _ord(entry[2]) & 64 and "<" or " " protect = entry[2] & 64 and "<" or " "
fname = entry[5:21] fname = entry[5:21]
if ftype == 'rel': if ftype == 'rel':
size = _ord(entry[23]) size = entry[23]
else: else:
size = _ord(entry[30]) + _ord(entry[31]) * 226 size = entry[30] + entry[31] * 226
self._dir_contents.append({'fname': self._map_filename(fname), self._dir_contents.append({'fname': self._map_filename(fname),
'ftype': type_verbose, 'ftype': type_verbose,
@@ -430,11 +395,7 @@ class Uc1541(object):
""" """
LOG.info("Removing file %s", dst) LOG.info("Removing file %s", dst)
dst = self._get_masked_fname(dst) dst = self._get_masked_fname(dst)
return self._call_command('delete', dst=dst)
if not self._call_command('delete', dst=dst):
return self._show_error()
return 0
def copyin(self, dst, src): def copyin(self, dst, src):
""" """
@@ -442,11 +403,7 @@ class Uc1541(object):
""" """
LOG.info("Copy into D64 %s as %s", src, dst) LOG.info("Copy into D64 %s as %s", src, dst)
dst = self._correct_fname(dst) dst = self._correct_fname(dst)
return self._call_command('write', src=src, dst=dst)
if not self._call_command('write', src=src, dst=dst):
return self._show_error()
return 0
def copyout(self, src, dst): def copyout(self, src, dst):
""" """
@@ -459,10 +416,7 @@ class Uc1541(object):
src = self._get_masked_fname(src) src = self._get_masked_fname(src)
if not self._call_command('read', src=src, dst=dst): return self._call_command('read', src=src, dst=dst)
return self._show_error()
return 0
def mkdir(self, dirname): def mkdir(self, dirname):
"""Not supported""" """Not supported"""
@@ -523,8 +477,9 @@ class Uc1541(object):
uid = os.getuid() uid = os.getuid()
gid = os.getgid() gid = os.getgid()
if not self._call_command('list'): res = self._call_command('list')
return self._show_error() if res != 0:
return res
idx = 0 idx = 0
for line in self.out.split("\n"): for line in self.out.split("\n"):
@@ -593,13 +548,16 @@ class Uc1541(object):
universal_newlines = True universal_newlines = True
if cmd in ['delete', 'write']: if cmd in ['delete', 'write']:
universal_newlines = False universal_newlines = False
self.out, self.err = Popen(command, (self.out,
self.err) = subprocess.Popen(command,
universal_newlines=universal_newlines, universal_newlines=universal_newlines,
stdout=PIPE, stderr=PIPE).communicate() stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
if self.err: if self.err:
LOG.debug('an err: %s', self.err) LOG.debug('an err: %s', self.err)
return not self.err return self._show_error()
return 0
CALL_MAP = {'list': lambda a: Uc1541(a.arch).list(), CALL_MAP = {'list': lambda a: Uc1541(a.arch).list(),
@@ -612,7 +570,7 @@ CALL_MAP = {'list': lambda a: Uc1541(a.arch).list(),
def parse_args(): def parse_args():
"""Use ArgumentParser to check for script arguments and execute.""" """Use ArgumentParser to check for script arguments and execute."""
parser = ArgumentParser() parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='supported commands', subparsers = parser.add_subparsers(help='supported commands',
dest='subcommand') dest='subcommand')
subparsers.required = True subparsers.required = True
@@ -659,44 +617,6 @@ def parse_args():
return args.func(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__":
LOG.debug("Script params: %s", str(sys.argv)) LOG.debug("Script params: %s", str(sys.argv))
try: sys.exit(parse_args())
from argparse import ArgumentParser
PARSE_FUNC = parse_args
except ImportError:
PARSE_FUNC = no_parse
sys.exit(PARSE_FUNC())