|
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 if isinstance(infile, (Carbon.File.FSSpec, Carbon.File.FSRef)): |
|
123 infile = infile.as_pathname() |
|
124 infile = open(infile, 'rb') |
|
125 |
|
126 asfile = AppleSingle(infile, verbose=verbose) |
|
127 asfile.tofile(outpath, resonly=resonly) |
|
128 |
|
129 def _test(): |
|
130 if len(sys.argv) < 3 or sys.argv[1] == '-r' and len(sys.argv) != 4: |
|
131 print 'Usage: applesingle.py [-r] applesinglefile decodedfile' |
|
132 sys.exit(1) |
|
133 if sys.argv[1] == '-r': |
|
134 resonly = True |
|
135 del sys.argv[1] |
|
136 else: |
|
137 resonly = False |
|
138 decode(sys.argv[1], sys.argv[2], resonly=resonly) |
|
139 |
|
140 if __name__ == '__main__': |
|
141 _test() |