1r"""Routines to decode AppleSingle files
2"""
3
4from warnings import warnpy3k
5warnpy3k("In 3.x, the applesingle module is removed.", stacklevel=2)
6
7import struct
8import sys
9try:
10    import MacOS
11    import Carbon.File
12except:
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.
28class Error(ValueError):
29    pass
30
31# File header format: magic, version, unused, number of entries
32AS_HEADER_FORMAT=">LL16sh"
33AS_HEADER_LENGTH=26
34# The flag words for AppleSingle
35AS_MAGIC=0x00051600
36AS_VERSION=0x00020000
37
38# Entry header format: id, offset, length
39AS_ENTRY_FORMAT=">lll"
40AS_ENTRY_LENGTH=12
41
42# The id values
43AS_DATAFORK=1
44AS_RESOURCEFORK=2
45AS_IGNORE=(3,4,5,6,8,9,10,11,12,13,14,15)
46
47class 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
107def 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
134def _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
145if __name__ == '__main__':
146    _test()
147