Allow a folder to be synced to/from disk

Previously only Volume objects had read_folder and write_folder methods.
This commit is contained in:
Elliot Nunn 2018-12-09 15:20:59 +08:00
parent b1af2b0353
commit d4536441e2
3 changed files with 173 additions and 169 deletions

View File

@ -1 +1,3 @@
from .main import * from .main import OutOfSpaceError, BadNameError
from .main import Volume
from .directory import File, Folder

View File

@ -1,4 +1,7 @@
import collections import collections
import os
from os import path
from macresources import make_rez_code, parse_rez_code, make_file, parse_file
class AbstractFolder(collections.MutableMapping): class AbstractFolder(collections.MutableMapping):
@ -70,3 +73,166 @@ class AbstractFolder(collections.MutableMapping):
else: else:
for each_path, each_child in childs_children: for each_path, each_child in childs_children:
yield (name,) + each_path, each_child yield (name,) + each_path, each_child
def read_folder(self, folder_path, date=0, mpw_dates=False):
def includefilter(n):
if n.startswith('.'): return False
if n.endswith('.rdump'): return True
if n.endswith('.idump'): return True
return True
def swapsep(n):
return n.replace(':', path.sep)
def mkbasename(n):
base, ext = path.splitext(n)
if ext in ('.rdump', '.idump'):
return base
else:
return n
self.crdate = self.mddate = self.bkdate = date
tmptree = {folder_path: self}
for dirpath, dirnames, filenames in os.walk(folder_path):
dirnames[:] = [swapsep(x) for x in dirnames if includefilter(x)]
filenames[:] = [swapsep(x) for x in filenames if includefilter(x)]
for dn in dirnames:
newdir = Folder()
newdir.crdate = newdir.mddate = newdir.bkdate = date
tmptree[dirpath][dn] = newdir
tmptree[path.join(dirpath, dn)] = newdir
for fn in filenames:
basename = mkbasename(fn)
fullbase = path.join(dirpath, basename)
fullpath = path.join(dirpath, fn)
try:
thefile = tmptree[fullbase]
except KeyError:
thefile = File()
thefile.real_t = 0 # for the MPW hack
thefile.crdate = thefile.mddate = thefile.bkdate = date
thefile.contributors = []
tmptree[fullbase] = thefile
if fn.endswith('.idump'):
with open(fullpath, 'rb') as f:
thefile.type = f.read(4)
thefile.creator = f.read(4)
elif fn.endswith('rdump'):
rez = open(fullpath, 'rb').read()
resources = parse_rez_code(rez)
resfork = make_file(resources, align=4)
thefile.rsrc = resfork
else:
thefile.data = open(fullpath, 'rb').read()
thefile.contributors.append(fullpath)
if mpw_dates:
thefile.real_t = max(thefile.real_t, path.getmtime(fullpath))
tmptree[dirpath][basename] = thefile
for pathtpl, obj in self.iter_paths():
try:
if obj.type == b'TEXT':
obj.data = obj.data.decode('utf8').replace('\r\n', '\r').replace('\n', '\r').encode('mac_roman')
except AttributeError:
pass
if mpw_dates:
all_real_times = set()
for pathtpl, obj in self.iter_paths():
try:
all_real_times.add(obj.real_t)
except AttributeError:
pass
ts2idx = {ts: idx for (idx, ts) in enumerate(sorted(set(all_real_times)))}
for pathtpl, obj in self.iter_paths():
try:
real_t = obj.real_t
except AttributeError:
pass
else:
fake_t = obj.crdate + 60 * ts2idx[real_t]
obj.crdate = obj.mddate = obj.bkdate = fake_t
def write_folder(self, folder_path):
def any_exists(at_path):
if path.exists(at_path): return True
if path.exists(at_path + '.rdump'): return True
if path.exists(at_path + '.idump'): return True
return False
written = []
for p, obj in self.iter_paths():
nativepath = path.join(folder_path, *(comp.replace(path.sep, ':') for comp in p))
if isinstance(obj, Folder):
os.makedirs(nativepath, exist_ok=True)
elif obj.mddate != obj.bkdate or not any_exists(nativepath):
data = obj.data
if obj.type == b'TEXT':
data = data.decode('mac_roman').replace('\r', os.linesep).encode('utf8')
rsrc = obj.rsrc
if rsrc:
rsrc = parse_file(rsrc)
rsrc = make_rez_code(rsrc, ascii_clean=True)
info = obj.type + obj.creator
if info == b'????????': info = b''
for thing, suffix in ((data, ''), (rsrc, '.rdump'), (info, '.idump')):
wholepath = nativepath + suffix
if thing or (suffix == '' and not rsrc):
written.append(wholepath)
with open(written[-1], 'wb') as f:
f.write(thing)
else:
try:
os.remove(wholepath)
except FileNotFoundError:
pass
if written:
t = path.getmtime(written[-1])
for w in written:
os.utime(w, (t, t))
class Folder(AbstractFolder):
def __init__(self):
super().__init__()
self.flags = 0 # help me!
self.x = 0 # where to put this spatially?
self.y = 0
self.crdate = self.mddate = self.bkdate = 0
class File:
def __init__(self):
self.type = b'????'
self.creator = b'????'
self.flags = 0 # help me!
self.x = 0 # where to put this spatially?
self.y = 0
self.locked = False
self.crdate = self.mddate = self.bkdate = 0
self.rsrc = bytearray()
self.data = bytearray()
def __str__(self):
typestr, creatorstr = (x.decode('mac_roman') for x in (self.type, self.creator))
dstr, rstr = (repr(bytes(x)) if 1 <= len(x) <= 32 else '%db' % len(x) for x in (self.data, self.rsrc))
return '[%s/%s] data=%s rsrc=%s' % (typestr, creatorstr, dstr, rstr)

View File

@ -1,8 +1,7 @@
import struct import struct
import os from macresources import Resource, make_file, parse_file
from os import path from . import btree, bitmanip
from macresources import Resource, make_file, parse_file, make_rez_code, parse_rez_code from .directory import AbstractFolder, Folder, File
from . import btree, bitmanip, directory
def _catalog_rec_sort(b): def _catalog_rec_sort(b):
@ -114,38 +113,7 @@ class BadNameError(Exception):
pass pass
class Folder(directory.AbstractFolder): class Volume(AbstractFolder):
def __init__(self):
super().__init__()
self.flags = 0 # help me!
self.x = 0 # where to put this spatially?
self.y = 0
self.crdate = self.mddate = self.bkdate = 0
class File:
def __init__(self):
self.type = b'????'
self.creator = b'????'
self.flags = 0 # help me!
self.x = 0 # where to put this spatially?
self.y = 0
self.locked = False
self.crdate = self.mddate = self.bkdate = 0
self.rsrc = bytearray()
self.data = bytearray()
def __str__(self):
typestr, creatorstr = (x.decode('mac_roman') for x in (self.type, self.creator))
dstr, rstr = (repr(bytes(x)) if 1 <= len(x) <= 32 else '%db' % len(x) for x in (self.data, self.rsrc))
return '[%s/%s] data=%s rsrc=%s' % (typestr, creatorstr, dstr, rstr)
class Volume(directory.AbstractFolder):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -496,135 +464,3 @@ class Volume(directory.AbstractFolder):
finalchunks.append(vib) finalchunks.append(vib)
finalchunks.append(bytes(512)) finalchunks.append(bytes(512))
return b''.join(finalchunks) return b''.join(finalchunks)
def read_folder(self, folder_path, date=0, mpw_dates=False):
def includefilter(n):
if n.startswith('.'): return False
if n.endswith('.rdump'): return True
if n.endswith('.idump'): return True
return True
def swapsep(n):
return n.replace(':', path.sep)
def mkbasename(n):
base, ext = path.splitext(n)
if ext in ('.rdump', '.idump'):
return base
else:
return n
self.crdate = self.mddate = self.bkdate = date
tmptree = {folder_path: self}
for dirpath, dirnames, filenames in os.walk(folder_path):
dirnames[:] = [swapsep(x) for x in dirnames if includefilter(x)]
filenames[:] = [swapsep(x) for x in filenames if includefilter(x)]
for dn in dirnames:
newdir = Folder()
newdir.crdate = newdir.mddate = newdir.bkdate = date
tmptree[dirpath][dn] = newdir
tmptree[path.join(dirpath, dn)] = newdir
for fn in filenames:
basename = mkbasename(fn)
fullbase = path.join(dirpath, basename)
fullpath = path.join(dirpath, fn)
try:
thefile = tmptree[fullbase]
except KeyError:
thefile = File()
thefile.real_t = 0 # for the MPW hack
thefile.crdate = thefile.mddate = thefile.bkdate = date
thefile.contributors = []
tmptree[fullbase] = thefile
if fn.endswith('.idump'):
with open(fullpath, 'rb') as f:
thefile.type = f.read(4)
thefile.creator = f.read(4)
elif fn.endswith('rdump'):
rez = open(fullpath, 'rb').read()
resources = parse_rez_code(rez)
resfork = make_file(resources, align=4)
thefile.rsrc = resfork
else:
thefile.data = open(fullpath, 'rb').read()
thefile.contributors.append(fullpath)
if mpw_dates:
thefile.real_t = max(thefile.real_t, path.getmtime(fullpath))
tmptree[dirpath][basename] = thefile
for pathtpl, obj in self.iter_paths():
try:
if obj.type == b'TEXT':
obj.data = obj.data.decode('utf8').replace('\r\n', '\r').replace('\n', '\r').encode('mac_roman')
except AttributeError:
pass
if mpw_dates:
all_real_times = set()
for pathtpl, obj in self.iter_paths():
try:
all_real_times.add(obj.real_t)
except AttributeError:
pass
ts2idx = {ts: idx for (idx, ts) in enumerate(sorted(set(all_real_times)))}
for pathtpl, obj in self.iter_paths():
try:
real_t = obj.real_t
except AttributeError:
pass
else:
fake_t = obj.crdate + 60 * ts2idx[real_t]
obj.crdate = obj.mddate = obj.bkdate = fake_t
def write_folder(self, folder_path):
def any_exists(at_path):
if path.exists(at_path): return True
if path.exists(at_path + '.rdump'): return True
if path.exists(at_path + '.idump'): return True
return False
written = []
for p, obj in self.iter_paths():
nativepath = path.join(folder_path, *(comp.replace(path.sep, ':') for comp in p))
if isinstance(obj, Folder):
os.makedirs(nativepath, exist_ok=True)
elif obj.mddate != obj.bkdate or not any_exists(nativepath):
data = obj.data
if obj.type == b'TEXT':
data = data.decode('mac_roman').replace('\r', os.linesep).encode('utf8')
rsrc = obj.rsrc
if rsrc:
rsrc = parse_file(rsrc)
rsrc = make_rez_code(rsrc, ascii_clean=True)
info = obj.type + obj.creator
if info == b'????????': info = b''
for thing, suffix in ((data, ''), (rsrc, '.rdump'), (info, '.idump')):
wholepath = nativepath + suffix
if thing or (suffix == '' and not rsrc):
written.append(wholepath)
with open(written[-1], 'wb') as f:
f.write(thing)
else:
try:
os.remove(wholepath)
except FileNotFoundError:
pass
if written:
t = path.getmtime(written[-1])
for w in written:
os.utime(w, (t, t))