mirror of
https://github.com/elliotnunn/machfs.git
synced 2024-11-24 16:31:20 +00:00
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:
parent
b1af2b0353
commit
d4536441e2
@ -1 +1,3 @@
|
|||||||
from .main import *
|
from .main import OutOfSpaceError, BadNameError
|
||||||
|
from .main import Volume
|
||||||
|
from .directory import File, Folder
|
||||||
|
@ -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)
|
||||||
|
172
machfs/main.py
172
machfs/main.py
@ -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))
|
|
||||||
|
Loading…
Reference in New Issue
Block a user