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()
|
---|