| @@ -0,0 +1,217 @@ | |||||
| #!/usr/bin/env python | |||||
| import hashlib | |||||
| import pasn1 | |||||
| import os.path | |||||
| import shutil | |||||
| import string | |||||
| import tempfile | |||||
| import unittest | |||||
| import uuid | |||||
| _validhashes = set([ 'sha256', 'sha512' ]) | |||||
| _hashlengths = { len(getattr(hashlib, x)().hexdigest()): x for x in _validhashes } | |||||
| # XXX - add validation | |||||
| class ObjWrap(object): | |||||
| '''This is a simple wrapper that turns a JSON object into a pythonesc | |||||
| object where attribute accesses work.''' | |||||
| def __init__(self, obj): | |||||
| self._obj = obj | |||||
| def __getattr__(self, k): | |||||
| return self._obj[k] | |||||
| def __getitem__(self, k): | |||||
| return self._obj[k] | |||||
| def __to_dict__(self): | |||||
| return self._obj | |||||
| def __eq__(self, o): | |||||
| return cmp(self._obj, o) == 0 | |||||
| def _trytodict(o): | |||||
| try: | |||||
| return 'dict', o.__to_dict__() | |||||
| except Exception: | |||||
| raise TypeError('unable to find __to_dict__ on %s' % type(o)) | |||||
| _asn1coder = pasn1.ASN1DictCoder(coerce=_trytodict) | |||||
| class ObjectStore(object): | |||||
| '''A container to store for the various Metadata objects.''' | |||||
| def __init__(self): | |||||
| self._uuids = {} | |||||
| self._hashes = {} | |||||
| @staticmethod | |||||
| def makehash(hashstr, strict=True): | |||||
| '''Take a hash string, and return a valid hash string from it. | |||||
| This makes sure that it is of the correct type and length. | |||||
| If strict is False, the function will detect the length and | |||||
| return a valid hash if one can be found.''' | |||||
| try: | |||||
| hash, value = hashstr.split(':') | |||||
| except ValueError: | |||||
| if strict: | |||||
| raise | |||||
| hash = _hashlengths[len(hashstr)] | |||||
| value = hashstr | |||||
| if strict and len(str(value).translate(None, string.hexdigits.lower())) != 0: | |||||
| raise ValueError('value has invalid hex digits (must be lower case)', value) | |||||
| if hash in _validhashes: | |||||
| return ':'.join((hash, value)) | |||||
| raise ValueError | |||||
| def __len__(self): | |||||
| return len(self._uuids) | |||||
| def store(self, fname): | |||||
| '''Write out the objects in the store to the file named | |||||
| fname.''' | |||||
| with open(fname, 'w') as fp: | |||||
| fp.write(_asn1coder.dumps(self._uuids.values())) | |||||
| def loadobj(self, obj): | |||||
| '''Load obj into the data store.''' | |||||
| if not isinstance(obj, ObjWrap): | |||||
| obj = ObjWrap(obj) | |||||
| id = uuid.UUID(obj.uuid) | |||||
| self._uuids[id] = obj | |||||
| for j in obj.hashes: | |||||
| h = self.makehash(j) | |||||
| self._hashes.setdefault(h, []).append(obj) | |||||
| def load(self, fname): | |||||
| '''Load objects from the provided file name. | |||||
| Basic validation will be done on the objects in the file. | |||||
| The objects will be accessible via other methods.''' | |||||
| with open(fname) as fp: | |||||
| objs = _asn1coder.loads(fp.read()) | |||||
| for i in objs: | |||||
| self.loadobj(i) | |||||
| def by_id(self, id): | |||||
| '''Look up an object by it's UUID.''' | |||||
| uid = uuid.UUID(id) | |||||
| return self._uuids[uid] | |||||
| def by_hash(self, hash): | |||||
| '''Look up an object by it's hash value.''' | |||||
| h = self.makehash(hash, strict=False) | |||||
| return self._hashes[h] | |||||
| class FileObject(object): | |||||
| def __init__(self, _dir, filename): | |||||
| self._dir = _dir | |||||
| self._fname = filename | |||||
| @property | |||||
| def filename(self): | |||||
| '''The name of the file.''' | |||||
| return self._fname | |||||
| @property | |||||
| def dir(self): | |||||
| '''The directory of the file.''' | |||||
| return self._dir | |||||
| @property | |||||
| def id(self): | |||||
| '''The UUID of the path to this file.''' | |||||
| # XXX make sure this is correct | |||||
| return uuid.uuid5(uuid.NAMESPACE_URL, 'someurl' + '/'.join(os.path.split(self._dir) + ( self._fname, ))) | |||||
| def enumeratedir(_dir): | |||||
| '''Enumerate all the files and directories (not recursive) in _dir. | |||||
| Returned is a list of FileObjects.''' | |||||
| return map(lambda x: FileObject(_dir, x), os.listdir(_dir)) | |||||
| class _TestCases(unittest.TestCase): | |||||
| def setUp(self): | |||||
| d = tempfile.mkdtemp() | |||||
| self.basetempdir = d | |||||
| self.tempdir = os.path.join(d, 'subdir') | |||||
| shutil.copytree(os.path.join('fixtures', 'testfiles'), | |||||
| self.tempdir) | |||||
| def tearDown(self): | |||||
| shutil.rmtree(self.basetempdir) | |||||
| self.tempdir = None | |||||
| def test_makehash(self): | |||||
| self.assertRaises(ValueError, ObjectStore.makehash, 'slkj') | |||||
| self.assertRaises(ValueError, ObjectStore.makehash, 'sha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ADA') | |||||
| self.assertEqual(ObjectStore.makehash('cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', strict=False), 'sha512:cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e') | |||||
| self.assertEqual(ObjectStore.makehash('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', strict=False), 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') | |||||
| def test_enumeratedir(self): | |||||
| files = enumeratedir(self.tempdir) | |||||
| ftest = files[0] | |||||
| fname = 'test.txt' | |||||
| self.assertEqual(ftest.filename, fname) | |||||
| self.assertEqual(ftest.dir, self.tempdir) | |||||
| self.assertEqual(ftest.id, uuid.uuid5(uuid.NAMESPACE_URL, | |||||
| 'someurl' + '/'.join(os.path.split(self.tempdir) + | |||||
| ( fname, )))) | |||||
| def test_objectstore(self): | |||||
| objst = ObjectStore() | |||||
| objst.load(os.path.join('fixtures', 'sample.data.pasn1')) | |||||
| objst.loadobj({ | |||||
| 'type': 'metadata', | |||||
| 'uuid': 'c9a1d1e2-3109-4efd-8948-577dc15e44e7', | |||||
| 'hashes': [ 'sha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada' ], | |||||
| 'lang': 'en', | |||||
| }) | |||||
| lst = objst.by_hash('91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada') | |||||
| self.assertEqual(len(lst), 2) | |||||
| byid = objst.by_id('3e466e06-45de-4ecc-84ba-2d2a3d970e96') | |||||
| self.assertIn(byid, lst) | |||||
| r = byid | |||||
| self.assertEqual(r.uuid, '3e466e06-45de-4ecc-84ba-2d2a3d970e96') | |||||
| self.assertEqual(r['dc:author'], 'John-Mark Gurney') | |||||
| objst.store('testfile.pasn1') | |||||
| with open('testfile.pasn1') as fp: | |||||
| objs = _asn1coder.loads(fp.read()) | |||||
| self.assertEqual(len(objs), len(objst)) | |||||
| for i in objs: | |||||
| self.assertEqual(objst.by_id(i['uuid']), i) | |||||