Home | History | Annotate | Download | only in plat-mac
      1 r"""Routines to decode AppleSingle files
      2 """
      3 
      4 from warnings import warnpy3k
      5 warnpy3k("In 3.x, the applesingle module is removed.", stacklevel=2)
      6 
      7 import struct
      8 import sys
      9 try:
     10     import MacOS
     11     import Carbon.File
     12 except:
     13     class MacOS:
     14         def openrf(path, mode):
     15             return open(path + '.rsrc', mode)
     16         openrf = classmethod(openrf)
     17     class Carbon:
     18         class File:
     19             class FSSpec:
     20                 pass
     21             class FSRef:
     22                 pass
     23             class Alias:
     24                 pass
     25 
     26 # all of the errors in this module are really errors in the input
     27 # so I think it should test positive against ValueError.
     28 class Error(ValueError):
     29     pass
     30 
     31 # File header format: magic, version, unused, number of entries
     32 AS_HEADER_FORMAT=">LL16sh"
     33 AS_HEADER_LENGTH=26
     34 # The flag words for AppleSingle
     35 AS_MAGIC=0x00051600
     36 AS_VERSION=0x00020000
     37 
     38 # Entry header format: id, offset, length
     39 AS_ENTRY_FORMAT=">lll"
     40 AS_ENTRY_LENGTH=12
     41 
     42 # The id values
     43 AS_DATAFORK=1
     44 AS_RESOURCEFORK=2
     45 AS_IGNORE=(3,4,5,6,8,9,10,11,12,13,14,15)
     46 
     47 class AppleSingle(object):
     48     datafork = None
     49     resourcefork = None
     50 
     51     def __init__(self, fileobj, verbose=False):
     52         header = fileobj.read(AS_HEADER_LENGTH)
     53         try:
     54             magic, version, ig, nentry = struct.unpack(AS_HEADER_FORMAT, header)
     55         except ValueError, arg:
     56             raise Error, "Unpack header error: %s" % (arg,)
     57         if verbose:
     58             print 'Magic:   0x%8.8x' % (magic,)
     59             print 'Version: 0x%8.8x' % (version,)
     60             print 'Entries: %d' % (nentry,)
     61         if magic != AS_MAGIC:
     62             raise Error, "Unknown AppleSingle magic number 0x%8.8x" % (magic,)
     63         if version != AS_VERSION:
     64             raise Error, "Unknown AppleSingle version number 0x%8.8x" % (version,)
     65         if nentry <= 0:
     66             raise Error, "AppleSingle file contains no forks"
     67         headers = [fileobj.read(AS_ENTRY_LENGTH) for i in xrange(nentry)]
     68         self.forks = []
     69         for hdr in headers:
     70             try:
     71                 restype, offset, length = struct.unpack(AS_ENTRY_FORMAT, hdr)
     72             except ValueError, arg:
     73                 raise Error, "Unpack entry error: %s" % (arg,)
     74             if verbose:
     75                 print "Fork %d, offset %d, length %d" % (restype, offset, length)
     76             fileobj.seek(offset)
     77             data = fileobj.read(length)
     78             if len(data) != length:
     79                 raise Error, "Short read: expected %d bytes got %d" % (length, len(data))
     80             self.forks.append((restype, data))
     81             if restype == AS_DATAFORK:
     82                 self.datafork = data
     83             elif restype == AS_RESOURCEFORK:
     84                 self.resourcefork = data
     85 
     86     def tofile(self, path, resonly=False):
     87         outfile = open(path, 'wb')
     88         data = False
     89         if resonly:
     90             if self.resourcefork is None:
     91                 raise Error, "No resource fork found"
     92             fp = open(path, 'wb')
     93             fp.write(self.resourcefork)
     94             fp.close()
     95         elif (self.resourcefork is None and self.datafork is None):
     96             raise Error, "No useful forks found"
     97         else:
     98             if self.datafork is not None:
     99                 fp = open(path, 'wb')
    100                 fp.write(self.datafork)
    101                 fp.close()
    102             if self.resourcefork is not None:
    103                 fp = MacOS.openrf(path, '*wb')
    104                 fp.write(self.resourcefork)
    105                 fp.close()
    106 
    107 def decode(infile, outpath, resonly=False, verbose=False):
    108     """decode(infile, outpath [, resonly=False, verbose=False])
    109 
    110     Creates a decoded file from an AppleSingle encoded file.
    111     If resonly is True, then it will create a regular file at
    112     outpath containing only the resource fork from infile.
    113     Otherwise it will create an AppleDouble file at outpath
    114     with the data and resource forks from infile.  On platforms
    115     without the MacOS module, it will create inpath and inpath+'.rsrc'
    116     with the data and resource forks respectively.
    117 
    118     """
    119     if not hasattr(infile, 'read'):
    120         if isinstance(infile, Carbon.File.Alias):
    121             infile = infile.ResolveAlias()[0]
    122 
    123         if hasattr(Carbon.File, "FSSpec"):
    124             if isinstance(infile, (Carbon.File.FSSpec, Carbon.File.FSRef)):
    125                 infile = infile.as_pathname()
    126         else:
    127             if isinstance(infile, Carbon.File.FSRef):
    128                 infile = infile.as_pathname()
    129         infile = open(infile, 'rb')
    130 
    131     asfile = AppleSingle(infile, verbose=verbose)
    132     asfile.tofile(outpath, resonly=resonly)
    133 
    134 def _test():
    135     if len(sys.argv) < 3 or sys.argv[1] == '-r' and len(sys.argv) != 4:
    136         print 'Usage: applesingle.py [-r] applesinglefile decodedfile'
    137         sys.exit(1)
    138     if sys.argv[1] == '-r':
    139         resonly = True
    140         del sys.argv[1]
    141     else:
    142         resonly = False
    143     decode(sys.argv[1], sys.argv[2], resonly=resonly)
    144 
    145 if __name__ == '__main__':
    146     _test()
    147