diff --git a/machfs/bitmanip.py b/machfs/bitmanip.py index 3a87ec4..279cd42 100644 --- a/machfs/bitmanip.py +++ b/machfs/bitmanip.py @@ -32,3 +32,8 @@ def chunkify(b, blksize): ab = b[i:i+blksize] if len(ab) < blksize: ab += bytes(blksize-len(ab)) yield ab + + +def pstring(orig): + macroman = orig.encode('mac_roman') + return bytes([len(macroman)]) + macroman diff --git a/machfs/directory.py b/machfs/directory.py index be12f06..6e31055 100644 --- a/machfs/directory.py +++ b/machfs/directory.py @@ -1,13 +1,6 @@ import collections -_CASE = list(range(256)) # cheating, fix this! - - -def _to_lower(orig): - return bytes(_CASE[x] for x in orig) - - class AbstractFolder(collections.MutableMapping): def __init__(self, from_dict=()): self._prefdict = {} # lowercase to preferred @@ -16,33 +9,35 @@ class AbstractFolder(collections.MutableMapping): def __setitem__(self, key, value): try: - key = key.encode('mac_roman') + key = key.decode('mac_roman') except AttributeError: pass - if len(key) > 31: - raise ValueError('Max filename length = 31') + key.encode('mac_roman') - lower = _to_lower(key) + if not (1 <= len(key) <= 31): + raise ValueError('Filename range 1-31 chars') + + lower = key.lower() self._prefdict[lower] = key self._maindict[lower] = value def __getitem__(self, key): try: - key = key.encode('mac_roman') + key = key.decode('mac_roman') except AttributeError: pass - lower = _to_lower(key) + lower = key.lower() return self._maindict[lower] def __delitem__(self, key): try: - value = value.encode('mac_roman') + value = value.decode('mac_roman') except AttributeError: pass - lower = _to_lower(key) + lower = key.lower() del self._maindict[lower] del self._prefdict[lower] @@ -58,7 +53,6 @@ class AbstractFolder(collections.MutableMapping): def iter_paths(self): for name, child in self.items(): - print(name, child) yield ((name,), child) try: childs_children = child.iter_paths() diff --git a/machfs/main.py b/machfs/main.py index ec16109..9bd87df 100644 --- a/machfs/main.py +++ b/machfs/main.py @@ -1,5 +1,4 @@ import struct -import collections from . import btree, bitmanip, directory @@ -101,19 +100,9 @@ class Volume(directory.AbstractFolder): self.bootblocks = bytes(1024) # optional; for booting HFS volumes self.crdate = 0 # date and time of volume creation self.lsmod = 0 # date and time of last modification - self._name = b'Untitled' - - @property - def name(self): - return self._name - @name.setter - def name(self, value): - if len(value) > 27: - raise ValueError('Max volume name length = 27') - self._name = value + self.name = 'Untitled' def read(self, from_volume): - self._dirtree = {} self.bootblocks = from_volume[:1024] drSigWord, drCrDate, drLsMod, drAtrb, drNmFls, \ @@ -127,7 +116,7 @@ class Volume(directory.AbstractFolder): self.crdate = drCrDate self.lsmod = drLsMod - self.name = drVN + self.name = drVN.decode('mac_roman') block2offset = lambda block: 512*drAlBlSt + drAlBlkSiz*block extent2bytes = lambda firstblk, blkcnt: from_volume[block2offset(firstblk):block2offset(firstblk+blkcnt)] @@ -140,7 +129,7 @@ class Volume(directory.AbstractFolder): pass cnids = {} - childrenof = collections.defaultdict(dict) + childlist = [] # list of (parent_cnid, child_name, child_object) tuples for rec in btree.dump_btree(extrec2bytes(drCTExtRec)): # create a directory tree from the catalog file @@ -156,9 +145,9 @@ class Volume(directory.AbstractFolder): datatype = (None, 'dir', 'file', 'dthread', 'fthread')[val[0]] datarec = val[2:] - print(datatype + '\t' + repr(key)) - print('\t', datarec) - print() + # print(datatype + '\t' + repr(key)) + # print('\t', datarec) + # print() if datatype == 'dir': dirFlags, dirVal, dirDirID, dirCrDat, dirMdDat, dirBkDat, dirUsrInfo, dirFndrInfo \ @@ -166,7 +155,7 @@ class Volume(directory.AbstractFolder): f = Folder() cnids[dirDirID] = f - childrenof[ckrParID][ckrCName] = f + childlist.append((ckrParID, ckrCName, f)) f.crdat, f.mddat, f.bkdat = dirCrDat, dirMdDat, dirBkDat @@ -181,7 +170,7 @@ class Volume(directory.AbstractFolder): f = File() cnids[filFlNum] = f - childrenof[ckrParID][ckrCName] = f + childlist.append((ckrParID, ckrCName, f)) f.crdat, f.mddat, f.bkdat = filCrDat, filMdDat, filBkDat f.type, f.creator, f.flags, f.x, f.y = struct.unpack_from('>4s4sHHH', filUsrWds) @@ -206,9 +195,10 @@ class Volume(directory.AbstractFolder): # elif datatype == 4: # print('fil thread:', rec) - for parent, children in childrenof.items(): - if parent != 1: # not the mythical parent of root! - cnids[parent].update(children) + for parent_cnid, child_name, child_obj in childlist: + if parent_cnid != 1: + parent_obj = cnids[parent_cnid] + parent_obj[child_name] = child_obj self.update(cnids[2]) @@ -290,8 +280,9 @@ class Volume(directory.AbstractFolder): if wrap.cnid == 1: continue obj = wrap.of + pstrname = bitmanip.pstring(path[-1]) - mainrec_key = struct.pack('>LB', path2wrap[path[:-1]].cnid, len(path[-1])) + path[-1] + mainrec_key = struct.pack('>L', path2wrap[path[:-1]].cnid) + pstrname if isinstance(wrap.of, File): drFilCnt += 1 @@ -339,7 +330,7 @@ class Volume(directory.AbstractFolder): thdrec_key = struct.pack('>Lx', wrap.cnid) thdrec_val_type = 4 if isinstance(wrap.of, File) else 3 - thdrec_val = struct.pack('>BxxxxxxxxxLB', thdrec_val_type, path2wrap[path[:-1]].cnid, len(path[-1])) + path[-1] + thdrec_val = struct.pack('>BxxxxxxxxxL', thdrec_val_type, path2wrap[path[:-1]].cnid) + pstrname catalog.append((thdrec_key, thdrec_val)) @@ -371,13 +362,16 @@ class Volume(directory.AbstractFolder): drWrCnt = 0 # ????volume write count drVCSize = drVBMCSize = drCtlCSize = 0 drAtrb = 1<<8 # volume attributes (hwlock, swlock, CLEANUNMOUNT, badblocks) - drVN = self.name + drVN = self.name.encode('mac_roman') drVolBkUp = 0 # date and time of last backup drVSeqNum = 0 # volume backup sequence number drFndrInfo = bytes(32) # information used by the Finder drCrDate = self.crdate drLsMod = self.lsmod + if not (1 <= len(drVN) <= 27): + raise ValueError('Volume name range 1-27 chars') + vib = struct.pack('>2sLLHHHHHLLHLH28pLHLLLHLL32sHHHLHHxxxxxxxxLHHxxxxxxxx', drSigWord, drCrDate, drLsMod, drAtrb, drNmFls, drVBMSt, drAllocPtr, drNmAlBlks, drAlBlkSiz, drClpSiz, drAlBlSt, diff --git a/test_all.py b/test_all.py index 1b81fb8..73f85c6 100644 --- a/test_all.py +++ b/test_all.py @@ -2,10 +2,16 @@ from machfs import * import os import time +def test_upperlower(): + h = Volume() + h['alpha'] = File() + assert h['alpha'] is h['ALPHA'] + assert list(h.keys()) == ['alpha'] + def test_roundtrip(): h = Volume() f = File() - h[b'single file'] = f + h['single file'] = f f.data = f.rsrc = b'1234' * 4096 copies = [h.write(800*1024)] @@ -16,21 +22,18 @@ def test_roundtrip(): assert copies[0] == copies[1] assert copies[1] == copies[2] + assert f.data in copies[-1] def test_macos_mount(): h = Volume() - h.name = b'ElmoTest' + h.name = 'ElmoTest' hf = File() hf.data = b'12345' * 10 for i in reversed(range(100)): - last = b'testfile-%03d' % i + last = 'testfile-%03d' % i h[last] = hf ser = h.write(10*1024*1024) - h2 = Volume() - h2.read(ser) - assert h2[b'testfile-000'].data == hf.data - open('/tmp/SMALL.dmg','wb').write(ser) os.system('hdiutil attach /tmp/SMALL.dmg') n = 10 @@ -44,6 +47,10 @@ def test_macos_mount(): pass else: break - recovered = open('/Volumes/ElmoTest/' + last.decode('ascii'),'rb').read() + recovered = open('/Volumes/ElmoTest/' + last,'rb').read() os.system('umount /Volumes/ElmoTest') assert recovered == hf.data + + # h2 = Volume() + # h2.read(ser) + # assert h2['testfile-000'].data == hf.data