commit 949879bd36dd77d54cba35a5dc1658dc92dc1d44
parent ca78e2ef2ab956963ca03cd05b8e7624dd667848
Author: lash <dev@holbrook.no>
Date: Mon, 7 Sep 2026 08:07:50 -0600
Add filesystem store implementation
Diffstat:
6 files changed, 171 insertions(+), 2 deletions(-)
diff --git a/adesse/attachment.py b/adesse/attachment.py
@@ -10,7 +10,7 @@ from adesse.ns import DF
class Attachment(Entity):
- def __init__(self, title, description, date, mime, typ=None, lang=None, uu=None):
+ def __init__(self, title, description, date, mime, typ=None, lang=None, uu=None, opener=None):
super().__init__(title, uu=uu)
self.description = description
self.dt = date.astimezone(datetime.timezone.utc)
@@ -18,6 +18,20 @@ class Attachment(Entity):
self.typ = typ
self.sum = None
self.tag = []
+ self.opener = opener
+ self.source = []
+
+
+ def srcadd(self, v):
+ self.source.append(v)
+
+
+ def srcopen(self):
+ for v in self.source:
+ f = self.opener(v)
+ if f != None:
+ return f
+ raise FileNotFoundError()
def set_sum(self, v, typ=None):
diff --git a/adesse/store/__init__.py b/adesse/store/__init__.py
@@ -0,0 +1 @@
+from .fs import FsStore
diff --git a/adesse/store/base.py b/adesse/store/base.py
@@ -0,0 +1,55 @@
+import hashlib
+import uuid
+
+
+def hash_for_io(f, hasher):
+ h = hasher()
+ h.update(f.read())
+ z = h.digest()
+ return z
+
+
+def sha256_for_path(filepath):
+ f = open(filepath, 'rb')
+ z = hash_for_io(f, hashlib.sha256)
+ f.close()
+ return z
+
+
+def sha512_for_path(filepath):
+ f = open(filepath, 'rb')
+ z = hash_for_io(f, hashlib.sha512)
+ f.close()
+ return z
+
+
+class BaseStore:
+
+ def __init__(self, seed=None):
+ self.transformers = {}
+ if seed == None:
+ seed = uuid.uuid4()
+ self.seed = seed
+ self.register_uuid()
+ self.canon = 'uuid'
+
+
+ def register(self, key, transformer):
+ self.transformers[key] = transformer
+
+
+ def register_sha256(self):
+ self.transformers['sha256'] = sha256_for_path
+
+
+ def register_sha512(self):
+ self.transformers['sha512'] = sha512_for_path
+
+
+ def register_uuid(self):
+ self.transformers['uuid'] = self.uuid_for_sum
+
+
+ def uuid_for_sum(self, v):
+ r = uuid.uuid5(self.seed, v)
+ return r.bytes
diff --git a/adesse/store/fs.py b/adesse/store/fs.py
@@ -0,0 +1,55 @@
+import os
+import shutil
+import logging
+import tempfile
+
+from .base import BaseStore
+
+logg = logging.getLogger('store.fs')
+
+
+class FsStore(BaseStore):
+
+ def __init__(self, path, seed=None):
+ super(FsStore, self).__init__(seed=seed)
+ self.path = os.path.realpath(path)
+ self.state = 0
+ self.canon_path = os.path.join(self.path, self.canon)
+
+
+ def _ensure_path(self):
+ if self.state & 1 == 0:
+ for k in self.transformers.keys():
+ d = os.path.join(self.path, k)
+ os.makedirs(d, exist_ok=True)
+ logg.info('created store dir {}'.format(d))
+ self.state = 1
+
+
+ def put(self, attachment):
+ self._ensure_path()
+ (f, fp) = tempfile.mkstemp()
+ w = os.fdopen(f, 'wb')
+ f = attachment.srcopen()
+ while True:
+ v = f.read()
+ if len(v) == 0:
+ break
+ w.write(v)
+ w.close()
+ f.close()
+ logg.debug('file contents in {}'.format(fp))
+
+ v = self.transformers[self.canon](fp)
+ dst = os.path.join(self.canon_path, v.hex())
+ shutil.copyfile(fp, dst)
+ logg.info('canon file to {}'.format(dst))
+
+ for k in self.transformers.keys():
+ v = self.transformers[k](fp)
+ if k == self.canon:
+ continue
+ else:
+ src = os.path.join(self.path, k, v.hex())
+ os.symlink(dst, src)
+ logg.info('file link {} is {}'.format(k, src))
diff --git a/test/test_store.py b/test/test_store.py
@@ -0,0 +1,45 @@
+import unittest
+import tempfile
+import shutil
+import os
+import logging
+import uuid
+import datetime
+
+from adesse.store import FsStore
+from adesse import Attachment
+
+thisdir = os.path.realpath(os.path.dirname(__file__))
+
+logging.basicConfig(level=logging.DEBUG)
+
+
+def testopener(v):
+ f = open(v, 'rb')
+ return f
+
+
+class TestFsStore(unittest.TestCase):
+
+ def setUp(self):
+ self.uubase = uuid.UUID('f253542c-78d1-4187-84f6-e15f3e686591')
+ self.storedir = tempfile.mkdtemp()
+ self.store = FsStore(self.storedir, seed=self.uubase)
+ self.store.register_sha256()
+ self.store.register_sha512()
+
+
+ def tearDown(self):
+ #shutil.rmtree(self.storedir)
+ pass
+
+
+ def test_put(self):
+ o = Attachment('title', 'description', datetime.datetime.now(datetime.UTC), 'application/pdf', lang='es', opener=testopener)
+ fp = os.path.join(thisdir, 'import.pdf')
+ o.srcadd(fp)
+ self.store.put(o)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/test_xmp.py b/test/test_xmp.py
@@ -27,7 +27,6 @@ class TestXmpLoad(unittest.TestCase):
r = xmp.attachment_from_xmp(fp, resource_file='import.pdf', search_dir=[thisdir], uuidns=self.uubase)
z = Serializer()
r.apply(z)
- print(z)
if __name__ == '__main__':