| r"""Routines to decode AppleSingle files | |
| """ | |
| from warnings import warnpy3k | |
| warnpy3k("In 3.x, the applesingle module is removed.", stacklevel=2) | |
| import struct | |
| import sys | |
| try: | |
| import MacOS | |
| import Carbon.File | |
| except: | |
| class MacOS: | |
| def openrf(path, mode): | |
| return open(path + '.rsrc', mode) | |
| openrf = classmethod(openrf) | |
| class Carbon: | |
| class File: | |
| class FSSpec: | |
| pass | |
| class FSRef: | |
| pass | |
| class Alias: | |
| pass | |
| # all of the errors in this module are really errors in the input | |
| # so I think it should test positive against ValueError. | |
| class Error(ValueError): | |
| pass | |
| # File header format: magic, version, unused, number of entries | |
| AS_HEADER_FORMAT=">LL16sh" | |
| AS_HEADER_LENGTH=26 | |
| # The flag words for AppleSingle | |
| AS_MAGIC=0x00051600 | |
| AS_VERSION=0x00020000 | |
| # Entry header format: id, offset, length | |
| AS_ENTRY_FORMAT=">lll" | |
| AS_ENTRY_LENGTH=12 | |
| # The id values | |
| AS_DATAFORK=1 | |
| AS_RESOURCEFORK=2 | |
| AS_IGNORE=(3,4,5,6,8,9,10,11,12,13,14,15) | |
| class AppleSingle(object): | |
| datafork = None | |
| resourcefork = None | |
| def __init__(self, fileobj, verbose=False): | |
| header = fileobj.read(AS_HEADER_LENGTH) | |
| try: | |
| magic, version, ig, nentry = struct.unpack(AS_HEADER_FORMAT, header) | |
| except ValueError, arg: | |
| raise Error, "Unpack header error: %s" % (arg,) | |
| if verbose: | |
| print 'Magic: 0x%8.8x' % (magic,) | |
| print 'Version: 0x%8.8x' % (version,) | |
| print 'Entries: %d' % (nentry,) | |
| if magic != AS_MAGIC: | |
| raise Error, "Unknown AppleSingle magic number 0x%8.8x" % (magic,) | |
| if version != AS_VERSION: | |
| raise Error, "Unknown AppleSingle version number 0x%8.8x" % (version,) | |
| if nentry <= 0: | |
| raise Error, "AppleSingle file contains no forks" | |
| headers = [fileobj.read(AS_ENTRY_LENGTH) for i in xrange(nentry)] | |
| self.forks = [] | |
| for hdr in headers: | |
| try: | |
| restype, offset, length = struct.unpack(AS_ENTRY_FORMAT, hdr) | |
| except ValueError, arg: | |
| raise Error, "Unpack entry error: %s" % (arg,) | |
| if verbose: | |
| print "Fork %d, offset %d, length %d" % (restype, offset, length) | |
| fileobj.seek(offset) | |
| data = fileobj.read(length) | |
| if len(data) != length: | |
| raise Error, "Short read: expected %d bytes got %d" % (length, len(data)) | |
| self.forks.append((restype, data)) | |
| if restype == AS_DATAFORK: | |
| self.datafork = data | |
| elif restype == AS_RESOURCEFORK: | |
| self.resourcefork = data | |
| def tofile(self, path, resonly=False): | |
| outfile = open(path, 'wb') | |
| data = False | |
| if resonly: | |
| if self.resourcefork is None: | |
| raise Error, "No resource fork found" | |
| fp = open(path, 'wb') | |
| fp.write(self.resourcefork) | |
| fp.close() | |
| elif (self.resourcefork is None and self.datafork is None): | |
| raise Error, "No useful forks found" | |
| else: | |
| if self.datafork is not None: | |
| fp = open(path, 'wb') | |
| fp.write(self.datafork) | |
| fp.close() | |
| if self.resourcefork is not None: | |
| fp = MacOS.openrf(path, '*wb') | |
| fp.write(self.resourcefork) | |
| fp.close() | |
| def decode(infile, outpath, resonly=False, verbose=False): | |
| """decode(infile, outpath [, resonly=False, verbose=False]) | |
| Creates a decoded file from an AppleSingle encoded file. | |
| If resonly is True, then it will create a regular file at | |
| outpath containing only the resource fork from infile. | |
| Otherwise it will create an AppleDouble file at outpath | |
| with the data and resource forks from infile. On platforms | |
| without the MacOS module, it will create inpath and inpath+'.rsrc' | |
| with the data and resource forks respectively. | |
| """ | |
| if not hasattr(infile, 'read'): | |
| if isinstance(infile, Carbon.File.Alias): | |
| infile = infile.ResolveAlias()[0] | |
| if hasattr(Carbon.File, "FSSpec"): | |
| if isinstance(infile, (Carbon.File.FSSpec, Carbon.File.FSRef)): | |
| infile = infile.as_pathname() | |
| else: | |
| if isinstance(infile, Carbon.File.FSRef): | |
| infile = infile.as_pathname() | |
| infile = open(infile, 'rb') | |
| asfile = AppleSingle(infile, verbose=verbose) | |
| asfile.tofile(outpath, resonly=resonly) | |
| def _test(): | |
| if len(sys.argv) < 3 or sys.argv[1] == '-r' and len(sys.argv) != 4: | |
| print 'Usage: applesingle.py [-r] applesinglefile decodedfile' | |
| sys.exit(1) | |
| if sys.argv[1] == '-r': | |
| resonly = True | |
| del sys.argv[1] | |
| else: | |
| resonly = False | |
| decode(sys.argv[1], sys.argv[2], resonly=resonly) | |
| if __name__ == '__main__': | |
| _test() |