mirror of
https://github.com/RasppleII/a2server.git
synced 2025-01-11 11:31:21 +00:00
support for ShrinkIt archives in addition to disk images
This commit is contained in:
parent
01d3ce1775
commit
53404e7784
@ -1,12 +1,12 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python2.7
|
||||||
# vim: set tabstop=4 shiftwidth=4 expandtab filetype=python:
|
# vim: set tabstop=4 shiftwidth=4 expandtab filetype=python:
|
||||||
|
|
||||||
"""cppo: Copy or catalog one or all files from a ProDOS raw disk image.
|
"""cppo: Copy or catalog one or all files from a ProDOS raw disk image.
|
||||||
|
|
||||||
copy all files:
|
copy all files:
|
||||||
cppo [-uc] [-ad|-e] imagefile target_directory
|
cppo [-uc] [-shk] [-ad|-e] imagefile target_directory
|
||||||
copy one file:
|
copy one file:
|
||||||
cppo [-uc] [-ad|-e] imagefile /FULL/PRODOS/FILE/PATH target_path
|
cppo [-uc] [-ad|-e] imagefile /FULL/PRODOS/PATH target_path
|
||||||
catalog image:
|
catalog image:
|
||||||
cppo [-uc] -cat imagefile
|
cppo [-uc] -cat imagefile
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ catalog image:
|
|||||||
forks, for adding to ShrinkIt archives with Nulib2
|
forks, for adding to ShrinkIt archives with Nulib2
|
||||||
using its -e option.
|
using its -e option.
|
||||||
-uc : Copy GS/OS mixed case filenames as uppercase.
|
-uc : Copy GS/OS mixed case filenames as uppercase.
|
||||||
|
-shk: Use ShrinkIt archive instead of disk image; must be used with -ad.
|
||||||
|
|
||||||
Wildcard matching/globbing (*) is not supported.
|
Wildcard matching/globbing (*) is not supported.
|
||||||
No verification or validation of the disk image is performed.
|
No verification or validation of the disk image is performed.
|
||||||
@ -39,6 +40,9 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import datetime
|
import datetime
|
||||||
|
import shutil
|
||||||
|
import errno
|
||||||
|
import uuid
|
||||||
|
|
||||||
# Intentially fails on pre-2.6 so user can see what's wrong
|
# Intentially fails on pre-2.6 so user can see what's wrong
|
||||||
b'ERROR: cppo requires Python 2.6 or later, including 3.x.'
|
b'ERROR: cppo requires Python 2.6 or later, including 3.x.'
|
||||||
@ -73,6 +77,7 @@ g.AD = 0
|
|||||||
g.EX = 0
|
g.EX = 0
|
||||||
g.DIR = 0
|
g.DIR = 0
|
||||||
g.UC = 0
|
g.UC = 0
|
||||||
|
g.SHK = 0
|
||||||
g.silent = 0
|
g.silent = 0
|
||||||
|
|
||||||
# functions
|
# functions
|
||||||
@ -137,8 +142,8 @@ def getFileName(arg1, arg2):
|
|||||||
for i in range(0, len(fileName)):
|
for i in range(0, len(fileName)):
|
||||||
if (caseMask[i] == "1"):
|
if (caseMask[i] == "1"):
|
||||||
fileName = (fileName[:i] +
|
fileName = (fileName[:i] +
|
||||||
fileName[i].lower() +
|
fileName[i:i+1].lower() +
|
||||||
fileName[(i+1):])
|
fileName[i+1:])
|
||||||
return fileName
|
return fileName
|
||||||
|
|
||||||
def getCaseMask(arg1, arg2):
|
def getCaseMask(arg1, arg2):
|
||||||
@ -223,8 +228,8 @@ def getWorkingDirName(arg1, arg2=None):
|
|||||||
for i in range(0, len(workingDirName)):
|
for i in range(0, len(workingDirName)):
|
||||||
if (caseMask[i] == "1"):
|
if (caseMask[i] == "1"):
|
||||||
workingDirName = (workingDirName[:i] +
|
workingDirName = (workingDirName[:i] +
|
||||||
workingDirName[i].lower() +
|
workingDirName[i:i+1].lower() +
|
||||||
workingDirName[(i+1):])
|
workingDirName[i+1:])
|
||||||
return workingDirName
|
return workingDirName
|
||||||
|
|
||||||
def getDirEntryCount(arg1):
|
def getDirEntryCount(arg1):
|
||||||
@ -237,6 +242,17 @@ def getDirNextChunkPointer(arg1):
|
|||||||
return (readcharDec(g.imageData, start+2) +
|
return (readcharDec(g.imageData, start+2) +
|
||||||
readcharDec(g.imageData, start+3)*256)
|
readcharDec(g.imageData, start+3)*256)
|
||||||
|
|
||||||
|
def toProDOSName(name):
|
||||||
|
i=0
|
||||||
|
if (name[0:1] == '.'): # eliminate leading period
|
||||||
|
name = name[1:]
|
||||||
|
for c in name:
|
||||||
|
if (c != '.' and not c.isalnum()):
|
||||||
|
name = name[:i] + '.' + name[i+1:]
|
||||||
|
i+=1
|
||||||
|
name = name[0:15]
|
||||||
|
return name
|
||||||
|
|
||||||
# -- script begins in earnest here
|
# -- script begins in earnest here
|
||||||
|
|
||||||
def copyFile(arg1, arg2):
|
def copyFile(arg1, arg2):
|
||||||
@ -324,19 +340,29 @@ def processDir(arg1, arg2=None, arg3=None, arg4=None, arg5=None):
|
|||||||
break
|
break
|
||||||
|
|
||||||
def processEntry(arg1, arg2):
|
def processEntry(arg1, arg2):
|
||||||
|
# arg1=block number, or subdir name if g.SHK=1
|
||||||
|
# arg2=index number of entry in directory, or file name if g.SHK=1
|
||||||
|
# ShrinkIt mode (g.SHK=1) requires -e extended filenames
|
||||||
|
|
||||||
'''
|
'''
|
||||||
print(getFileName(arg1, arg2), getStorageType(arg1, arg2),
|
print(getFileName(arg1, arg2), getStorageType(arg1, arg2),
|
||||||
getFileType(arg1, arg2), getKeyPointer(arg1, arg2),
|
getFileType(arg1, arg2), getKeyPointer(arg1, arg2),
|
||||||
getFileLength(arg1, arg2), getAuxType(arg1, arg2),
|
getFileLength(arg1, arg2), getAuxType(arg1, arg2),
|
||||||
getCreationDate(arg1, arg2), getModifiedDate(arg1, arg2))
|
getCreationDate(arg1, arg2), getModifiedDate(arg1, arg2))
|
||||||
'''
|
'''
|
||||||
g.activeFileName = getFileName(arg1 ,arg2).decode("L1")
|
shk_rfork = (1 if (g.SHK and (arg2[-1:] == "r")) else 0)
|
||||||
g.activeFileSize = getFileLength(arg1, arg2)
|
if not g.SHK:
|
||||||
|
g.activeFileName = getFileName(arg1 ,arg2).decode("L1")
|
||||||
|
g.activeFileSize = getFileLength(arg1, arg2)
|
||||||
|
else:
|
||||||
|
filePath = (os.path.join(arg1, arg2))
|
||||||
|
g.activeFileName = arg2.split('#')[0]
|
||||||
|
|
||||||
if ((not g.PDOSPATH_INDEX) or
|
if (not g.PDOSPATH_INDEX or
|
||||||
(g.activeFileName.upper() == g.PDOSPATH_SEGMENT.upper())):
|
g.activeFileName.upper() == g.PDOSPATH_SEGMENT.upper()):
|
||||||
|
|
||||||
if (getStorageType(arg1, arg2) == 13): # if ProDOS directory
|
if (not g.SHK and
|
||||||
|
getStorageType(arg1, arg2) == 13): # if ProDOS directory
|
||||||
if not g.PDOSPATH_INDEX:
|
if not g.PDOSPATH_INDEX:
|
||||||
g.targetDir = (g.targetDir + "/" + g.activeFileName)
|
g.targetDir = (g.targetDir + "/" + g.activeFileName)
|
||||||
g.ADdir = (g.targetDir + "/.AppleDouble")
|
g.ADdir = (g.targetDir + "/.AppleDouble")
|
||||||
@ -352,9 +378,10 @@ def processEntry(arg1, arg2):
|
|||||||
if not g.PDOSPATH_INDEX:
|
if not g.PDOSPATH_INDEX:
|
||||||
g.targetDir = g.targetDir.rsplit("/", 1)[0]
|
g.targetDir = g.targetDir.rsplit("/", 1)[0]
|
||||||
g.ADdir = (g.targetDir + "/.AppleDouble")
|
g.ADdir = (g.targetDir + "/.AppleDouble")
|
||||||
else: # if ProDOS file
|
else: # if ProDOS file either from image or ShrinkIt archive
|
||||||
if not g.PDOSPATH_INDEX:
|
if not g.PDOSPATH_INDEX:
|
||||||
print(" " + g.activeFileName)
|
print(" " + g.activeFileName +
|
||||||
|
(" [resource fork]" if shk_rfork else ""))
|
||||||
if g.DIR:
|
if g.DIR:
|
||||||
return
|
return
|
||||||
if not g.targetName:
|
if not g.targetName:
|
||||||
@ -364,19 +391,32 @@ def processEntry(arg1, arg2):
|
|||||||
getFileType(arg1, arg2).lower() +
|
getFileType(arg1, arg2).lower() +
|
||||||
getAuxType(arg1, arg2).lower())
|
getAuxType(arg1, arg2).lower())
|
||||||
touch(g.targetDir + "/" + g.targetName)
|
touch(g.targetDir + "/" + g.targetName)
|
||||||
if g.AD: makeADfile()
|
if g.AD:
|
||||||
copyFile(arg1, arg2)
|
makeADfile()
|
||||||
saveFile((g.targetDir + "/" + g.targetName), g.outFileData)
|
if not g.SHK: # ProDOS image
|
||||||
creationDate = getCreationDate(arg1, arg2)
|
copyFile(arg1, arg2)
|
||||||
modifiedDate = getModifiedDate(arg1, arg2)
|
saveFile((g.targetDir + "/" + g.targetName), g.outFileData)
|
||||||
if (creationDate is None and modifiedDate is not None):
|
creationDate = getCreationDate(arg1, arg2)
|
||||||
|
modifiedDate = getModifiedDate(arg1, arg2)
|
||||||
|
if (creationDate is None and modifiedDate is not None):
|
||||||
|
creationDate = modifiedDate
|
||||||
|
elif (creationDate is not None and modifiedDate is None):
|
||||||
|
modifiedDate = creationDate
|
||||||
|
elif (creationDate is None and modifiedDate is None):
|
||||||
|
creationDate = (datetime.datetime.today() -
|
||||||
|
datetime.datetime(1970,1,1)).days*24*60*60
|
||||||
|
modifiedDate = creationDate
|
||||||
|
else: # ShrinkIt archive
|
||||||
|
modifiedDate = int(time.mktime(
|
||||||
|
time.strptime(
|
||||||
|
time.ctime(
|
||||||
|
os.path.getmtime(filePath)))))
|
||||||
creationDate = modifiedDate
|
creationDate = modifiedDate
|
||||||
elif (creationDate is not None and modifiedDate is None):
|
if not shk_rfork:
|
||||||
modifiedDate = creationDate
|
shutil.move(filePath, (g.targetDir + "/" + g.targetName))
|
||||||
elif (creationDate is None and modifiedDate is None):
|
else: # shk_rfork:
|
||||||
creationDate = (datetime.datetime.today() -
|
with open(filePath, 'rb') as infile:
|
||||||
datetime.datetime(1970,1,1)).days*24*60*60
|
g.adFileData += infile.read()
|
||||||
modifiedDate = creationDate
|
|
||||||
if g.AD: # AppleDouble
|
if g.AD: # AppleDouble
|
||||||
# set dates
|
# set dates
|
||||||
ADfilePath = (g.ADdir + "/" + g.targetName)
|
ADfilePath = (g.ADdir + "/" + g.targetName)
|
||||||
@ -390,8 +430,10 @@ def processEntry(arg1, arg2):
|
|||||||
writechars(g.adFileData, 653, b'p')
|
writechars(g.adFileData, 653, b'p')
|
||||||
writecharsHex(g.adFileData,
|
writecharsHex(g.adFileData,
|
||||||
654,
|
654,
|
||||||
(getFileType(arg1, arg2) +
|
((getFileType(arg1, arg2) +
|
||||||
getAuxType(arg1, arg2)))
|
getAuxType(arg1, arg2)) if not g.SHK else
|
||||||
|
(arg2.split('#')[1][0:2] +
|
||||||
|
arg2.split('#')[1][2:6])))
|
||||||
writechars(g.adFileData, 657, b'pdos')
|
writechars(g.adFileData, 657, b'pdos')
|
||||||
saveFile(ADfilePath, g.adFileData)
|
saveFile(ADfilePath, g.adFileData)
|
||||||
touch((g.targetDir + "/" + g.targetName), modifiedDate)
|
touch((g.targetDir + "/" + g.targetName), modifiedDate)
|
||||||
@ -511,9 +553,9 @@ def syncExit():
|
|||||||
# saveFile(g.imageFile, g.imageData)
|
# saveFile(g.imageFile, g.imageData)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def usage():
|
def usage(exitcode=1):
|
||||||
print(sys.modules[__name__].__doc__)
|
print(sys.modules[__name__].__doc__)
|
||||||
sys.exit(1)
|
sys.exit(exitcode)
|
||||||
|
|
||||||
# --- ID bashbyter functions (adapted)
|
# --- ID bashbyter functions (adapted)
|
||||||
|
|
||||||
@ -678,7 +720,10 @@ def writecharsHex(arg1, arg2, arg3):
|
|||||||
def slyce(val, start_pos=0, length=1, reverse=False):
|
def slyce(val, start_pos=0, length=1, reverse=False):
|
||||||
"""returns slice of object (but not a slice object)
|
"""returns slice of object (but not a slice object)
|
||||||
allows specifying length, and 3.x "bytes" consistency"""
|
allows specifying length, and 3.x "bytes" consistency"""
|
||||||
the_slyce = val[start_pos:start_pos+length]
|
if (start_pos < 0):
|
||||||
|
the_slyce = val[start_pos:]
|
||||||
|
else:
|
||||||
|
the_slyce = val[start_pos:start_pos+length]
|
||||||
return (the_slyce[::-1] if reverse else the_slyce)
|
return (the_slyce[::-1] if reverse else the_slyce)
|
||||||
|
|
||||||
def to_hex(val):
|
def to_hex(val):
|
||||||
@ -780,6 +825,7 @@ def get_object_names(cls, include_subclasses=True):
|
|||||||
|
|
||||||
def touch(filePath, modTime=None):
|
def touch(filePath, modTime=None):
|
||||||
# http://stackoverflow.com/questions/1158076/implement-touch-using-python
|
# http://stackoverflow.com/questions/1158076/implement-touch-using-python
|
||||||
|
# print(filePath)
|
||||||
import os
|
import os
|
||||||
if (os.name == "nt"):
|
if (os.name == "nt"):
|
||||||
if filePath[-1] == ".": filePath += "-"
|
if filePath[-1] == ".": filePath += "-"
|
||||||
@ -804,8 +850,9 @@ def makedirs(dirPath):
|
|||||||
dirPath = dirPath.replace("./", ".-/")
|
dirPath = dirPath.replace("./", ".-/")
|
||||||
try:
|
try:
|
||||||
os.makedirs(dirPath)
|
os.makedirs(dirPath)
|
||||||
except FileExistsError:
|
except OSError as e:
|
||||||
pass
|
if (e.errno != errno.EEXIST):
|
||||||
|
raise
|
||||||
|
|
||||||
def loadFile(filePath):
|
def loadFile(filePath):
|
||||||
import os
|
import os
|
||||||
@ -844,26 +891,36 @@ args = sys.argv
|
|||||||
if (len(args) == 1):
|
if (len(args) == 1):
|
||||||
usage()
|
usage()
|
||||||
|
|
||||||
if (args[1] == "-s"):
|
while (slyce(args[1],0,1) == "-"):
|
||||||
g.silent = 1
|
if (args[1] == "-s"):
|
||||||
args = args[1:] #shift
|
g.silent = 1
|
||||||
|
args = args[1:] #shift
|
||||||
|
|
||||||
if (args[1] == "-uc"):
|
elif (args[1] == "-uc"):
|
||||||
g.UC = 1
|
g.UC = 1
|
||||||
args = args[1:] #shift
|
args = args[1:] #shift
|
||||||
|
|
||||||
if (args[1] == "-ad"):
|
elif (args[1] == "-ad"):
|
||||||
g.AD = 1
|
if g.EX: usage()
|
||||||
args = args[1:] #shift
|
g.AD = 1
|
||||||
|
args = args[1:] #shift
|
||||||
|
|
||||||
if (args[1] == "-e"):
|
elif (args[1] == "-shk"):
|
||||||
if g.AD: usage()
|
g.SHK = 1
|
||||||
g.EX = 1
|
args = args[1:] #shift
|
||||||
args = args[1:] #shift
|
|
||||||
|
|
||||||
if (args[1] == "-cat"):
|
elif (args[1] == "-e"):
|
||||||
g.DIR = 1
|
if g.AD: usage()
|
||||||
args = args[1:]
|
g.EX = 1
|
||||||
|
args = args[1:] #shift
|
||||||
|
|
||||||
|
elif (args[1] == "-cat"):
|
||||||
|
if g.AD or g.EX: usage()
|
||||||
|
g.DIR = 1
|
||||||
|
args = args[1:] #shift
|
||||||
|
|
||||||
|
else:
|
||||||
|
usage()
|
||||||
|
|
||||||
if not ((g.DIR and len(args) >= 2) or (len(args) >= 3)):
|
if not ((g.DIR and len(args) >= 2) or (len(args) >= 3)):
|
||||||
usage()
|
usage()
|
||||||
@ -876,6 +933,62 @@ g.imageFile = args[1]
|
|||||||
if not os.path.isfile(g.imageFile):
|
if not os.path.isfile(g.imageFile):
|
||||||
print("Source " + g.imageFile + " was not found.")
|
print("Source " + g.imageFile + " was not found.")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
# automatically set ShrinkIt mode if extension suggests it
|
||||||
|
if (g.SHK or
|
||||||
|
g.imageFile[-3:].lower() == "shk" or
|
||||||
|
g.imageFile[-3:].lower() == "sdk" or
|
||||||
|
g.imageFile[-3:].lower() == "bxy"):
|
||||||
|
if not g.AD:
|
||||||
|
print("ShrinkIt archives must be used with -ad option.")
|
||||||
|
sys.exit(2)
|
||||||
|
elif (g.DIR or g.EX):
|
||||||
|
usage()
|
||||||
|
elif (len(args) == 4):
|
||||||
|
print("Only entire ShrinkIt archives can be extracted, not one file.")
|
||||||
|
usage(2)
|
||||||
|
elif (not os.path.isfile("/usr/local/bin/nulib2") and
|
||||||
|
not os.path.isfile("/usr/bin/nulib2") and
|
||||||
|
not os.path.isfile("nulib2")):
|
||||||
|
print("Nulib2 not found. Can't expand ShrinkIt archive.")
|
||||||
|
sys.exit(2)
|
||||||
|
else:
|
||||||
|
g.SHK = 1
|
||||||
|
if g.SHK:
|
||||||
|
unshkdir = ("/tmp/cppo-" + str(uuid.uuid4()))
|
||||||
|
makedirs(unshkdir)
|
||||||
|
os.system("cd " + unshkdir + "; " +
|
||||||
|
"nulib2 -xse " + os.path.abspath(g.imageFile) + " > /dev/null")
|
||||||
|
fileNames = [name for name in os.listdir(unshkdir)
|
||||||
|
if not name.startswith(".")]
|
||||||
|
if (len(fileNames) == 1 and os.path.isdir(unshkdir + "/" + fileNames[0])):
|
||||||
|
oneDir = True
|
||||||
|
volumeName=toProDOSName(fileNames[0])
|
||||||
|
else:
|
||||||
|
oneDir = False
|
||||||
|
volumeName = toProDOSName(os.path.basename(g.imageFile))
|
||||||
|
imageFileExt = os.path.splitext(g.imageFile)[1].upper()
|
||||||
|
if (volumeName[-4:].lower() == ".shk" or
|
||||||
|
volumeName[-4:].lower() == ".sdk" or
|
||||||
|
volumeName[-4:].lower() == ".bxy"):
|
||||||
|
volumeName = volumeName[0:-4]
|
||||||
|
# recursively process unshrunk archive hierarchy
|
||||||
|
for dirName, subdirList, fileList in os.walk(unshkdir):
|
||||||
|
subdirList.sort()
|
||||||
|
g.targetDir = (args[2] + (("/" + volumeName) if not oneDir else "") +
|
||||||
|
("/" if (dirName.count('/') > 2) else "") +
|
||||||
|
"/".join(dirName.split('/')[3:])) # chop off tempdir
|
||||||
|
g.ADdir = (g.targetDir + "/.AppleDouble")
|
||||||
|
print("/".join(dirName.split('/')[3:]))
|
||||||
|
makedirs(g.targetDir)
|
||||||
|
makedirs(g.ADdir)
|
||||||
|
for fname in sorted(fileList):
|
||||||
|
processEntry(dirName, fname)
|
||||||
|
if (unshkdir.count('/') > 2):
|
||||||
|
unshkdir = "/".join(dirName.split('/')[0:3])
|
||||||
|
shutil.rmtree(unshkdir, True)
|
||||||
|
syncExit()
|
||||||
|
|
||||||
g.imageData = loadFile(g.imageFile)
|
g.imageData = loadFile(g.imageFile)
|
||||||
|
|
||||||
if (len(args) == 4):
|
if (len(args) == 4):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user