blob: 13962f6098286cb8974aa0baf318c313d628b647 [file] [log] [blame]
Jack Jansen3997f582003-11-18 23:09:19 +00001r"""Routines to decode AppleSingle files
2"""
Benjamin Peterson23681932008-05-12 21:42:13 +00003
4from warnings import warnpy3k
Benjamin Petersona6864e02008-07-14 17:42:17 +00005warnpy3k("In 3.x, the applesingle module is removed.", stacklevel=2)
Benjamin Peterson23681932008-05-12 21:42:13 +00006
Jack Jansena48d4ea2001-09-09 00:35:19 +00007import struct
Jack Jansena48d4ea2001-09-09 00:35:19 +00008import sys
Jack Jansen3997f582003-11-18 23:09:19 +00009try:
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
Jack Jansena48d4ea2001-09-09 00:35:19 +000025
Jack Jansen3997f582003-11-18 23:09:19 +000026# 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
Jack Jansena48d4ea2001-09-09 00:35:19 +000030
31# File header format: magic, version, unused, number of entries
Ronald Oussoren6c107482006-04-17 13:40:08 +000032AS_HEADER_FORMAT=">LL16sh"
Jack Jansena48d4ea2001-09-09 00:35:19 +000033AS_HEADER_LENGTH=26
34# The flag words for AppleSingle
35AS_MAGIC=0x00051600
36AS_VERSION=0x00020000
37
38# Entry header format: id, offset, length
Jack Jansende540e22006-02-23 14:54:30 +000039AS_ENTRY_FORMAT=">lll"
Jack Jansena48d4ea2001-09-09 00:35:19 +000040AS_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
Jack Jansen3997f582003-11-18 23:09:19 +000047class AppleSingle(object):
48 datafork = None
49 resourcefork = None
Jack Jansena48d4ea2001-09-09 00:35:19 +000050
Jack Jansen3997f582003-11-18 23:09:19 +000051 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
Tim Peters182b5ac2004-07-18 06:16:08 +000085
Jack Jansen3997f582003-11-18 23:09:19 +000086 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()
Tim Peters182b5ac2004-07-18 06:16:08 +0000106
Jack Jansen3997f582003-11-18 23:09:19 +0000107def 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.
Tim Peters182b5ac2004-07-18 06:16:08 +0000111 If resonly is True, then it will create a regular file at
Jack Jansen3997f582003-11-18 23:09:19 +0000112 outpath containing only the resource fork from infile.
113 Otherwise it will create an AppleDouble file at outpath
Tim Peters182b5ac2004-07-18 06:16:08 +0000114 with the data and resource forks from infile. On platforms
Jack Jansen3997f582003-11-18 23:09:19 +0000115 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]
Ronald Oussoren25967582009-09-06 10:00:26 +0000122
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()
Jack Jansen3997f582003-11-18 23:09:19 +0000129 infile = open(infile, 'rb')
130
Neal Norwitz8142cb62006-03-22 07:13:30 +0000131 asfile = AppleSingle(infile, verbose=verbose)
132 asfile.tofile(outpath, resonly=resonly)
Tim Peters182b5ac2004-07-18 06:16:08 +0000133
Jack Jansena48d4ea2001-09-09 00:35:19 +0000134def _test():
Jack Jansen0ae32202003-04-09 13:25:43 +0000135 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':
Jack Jansen3997f582003-11-18 23:09:19 +0000139 resonly = True
Jack Jansen0ae32202003-04-09 13:25:43 +0000140 del sys.argv[1]
141 else:
Jack Jansen3997f582003-11-18 23:09:19 +0000142 resonly = False
Jack Jansen0ae32202003-04-09 13:25:43 +0000143 decode(sys.argv[1], sys.argv[2], resonly=resonly)
Tim Peters182b5ac2004-07-18 06:16:08 +0000144
Jack Jansena48d4ea2001-09-09 00:35:19 +0000145if __name__ == '__main__':
Jack Jansen0ae32202003-04-09 13:25:43 +0000146 _test()