# applesingle - a module to decode AppleSingle files
import struct
import MacOS
import sys

Error="applesingle.Error"

verbose=0

# File header format: magic, version, unused, number of entries
AS_HEADER_FORMAT="ll16sh"
AS_HEADER_LENGTH=26
# The flag words for AppleSingle
AS_MAGIC=0x00051600
AS_VERSION=0x00020000

# Entry header format: id, offset, length
AS_ENTRY_FORMAT="lll"
AS_ENTRY_LENGTH=12

# The id values
AS_DATAFORK=1
AS_RESOURCEFORK=2
AS_IGNORE=(3,4,5,6,8,9,10,11,12,13,14,15)

def decode(input, output, resonly=0):
    if type(input) == type(''):
        input = open(input, 'rb')
    # Should we also test for FSSpecs or FSRefs?
    header = input.read(AS_HEADER_LENGTH)
    try:
        magic, version, dummy, nentry = struct.unpack(AS_HEADER_FORMAT, header)
    except ValueError, arg:
        raise Error, "Unpack header error: %s"%arg
    if verbose:
        print 'Magic:   0x%8.8x'%magic
        print 'Version: 0x%8.8x'%version
        print 'Entries: %d'%nentry
    if magic != AS_MAGIC:
        raise Error, 'Unknown AppleSingle magic number 0x%8.8x'%magic
    if version != AS_VERSION:
        raise Error, 'Unknown AppleSingle version number 0x%8.8x'%version
    if nentry <= 0:
        raise Error, "AppleSingle file contains no forks"
    headers = [input.read(AS_ENTRY_LENGTH) for i in range(nentry)]
    didwork = 0
    for hdr in headers:
        try:
            id, offset, length = struct.unpack(AS_ENTRY_FORMAT, hdr)
        except ValueError, arg:
            raise Error, "Unpack entry error: %s"%arg
        if verbose:
            print 'Fork %d, offset %d, length %d'%(id, offset, length)
        input.seek(offset)
        if length == 0:
            data = ''
        else:
            data = input.read(length)
        if len(data) != length:
            raise Error, 'Short read: expected %d bytes got %d'%(length, len(data))
        if id == AS_DATAFORK:
            if verbose:
                print '  (data fork)'
            if not resonly:
                didwork = 1
                fp = open(output, 'wb')
                fp.write(data)
                fp.close()
        elif id == AS_RESOURCEFORK:
            didwork = 1
            if verbose:
                print '  (resource fork)'
            if resonly:
                fp = open(output, 'wb')
            else:
                fp = MacOS.openrf(output, 'wb')
            fp.write(data)
            fp.close()
        elif id in AS_IGNORE:
            if verbose:
                print '  (ignored)'
        else:
            raise Error, 'Unknown fork type %d'%id
    if not didwork:
        raise Error, 'No useful forks found'

def _test():
    if len(sys.argv) < 3 or sys.argv[1] == '-r' and len(sys.argv) != 4:
        print 'Usage: applesingle.py [-r] applesinglefile decodedfile'
        sys.exit(1)
    if sys.argv[1] == '-r':
        resonly = 1
        del sys.argv[1]
    else:
        resonly = 0
    decode(sys.argv[1], sys.argv[2], resonly=resonly)
    
if __name__ == '__main__':
    _test()
    