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