| @@ -1,5 +1,7 @@ | |||||
| #!/usr/bin/env python | #!/usr/bin/env python | ||||
| #import pdb, sys; mypdb = pdb.Pdb(stdout=sys.stderr); mypdb.set_trace() | |||||
| import copy | import copy | ||||
| import datetime | import datetime | ||||
| import hashlib | import hashlib | ||||
| @@ -26,6 +28,8 @@ def _iterdictlist(obj): | |||||
| itms.sort() | itms.sort() | ||||
| for k, v in itms: | for k, v in itms: | ||||
| if isinstance(v, list): | if isinstance(v, list): | ||||
| v = v[:] | |||||
| v.sort() | |||||
| for i in v: | for i in v: | ||||
| yield k, i | yield k, i | ||||
| else: | else: | ||||
| @@ -78,8 +82,8 @@ class MDBase(object): | |||||
| `ty`) | `ty`) | ||||
| def new_version(self, *args): | def new_version(self, *args): | ||||
| '''Add the property k as an additional one (or new on if | |||||
| first), with the value v.''' | |||||
| '''For each k, v pari, add the property k as an additional one | |||||
| (or new on if first), with the value v.''' | |||||
| obj = copy.deepcopy(self._obj) | obj = copy.deepcopy(self._obj) | ||||
| @@ -113,10 +117,12 @@ class MetaData(MDBase): | |||||
| _type = 'metadata' | _type = 'metadata' | ||||
| def _trytodict(o): | def _trytodict(o): | ||||
| if isinstance(o, uuid.UUID): | |||||
| return 'unicode', str(o) | |||||
| try: | try: | ||||
| return 'dict', o.__to_dict__() | return 'dict', o.__to_dict__() | ||||
| except Exception: # pragma: no cover | except Exception: # pragma: no cover | ||||
| raise TypeError('unable to find __to_dict__ on %s' % type(o)) | |||||
| raise TypeError('unable to find __to_dict__ on %s: %s' % (type(o), `o`)) | |||||
| _asn1coder = pasn1.ASN1DictCoder(coerce=_trytodict) | _asn1coder = pasn1.ASN1DictCoder(coerce=_trytodict) | ||||
| @@ -271,7 +277,7 @@ class FileObject(MDBase): | |||||
| 'id': cls.make_id(filename), | 'id': cls.make_id(filename), | ||||
| 'mtime': datetime.datetime.utcfromtimestamp(s.st_mtime), | 'mtime': datetime.datetime.utcfromtimestamp(s.st_mtime), | ||||
| 'size': s.st_size, | 'size': s.st_size, | ||||
| 'hashes': ( _hashfile(filename), ), | |||||
| 'hashes': [ _hashfile(filename), ], | |||||
| } | } | ||||
| @@ -289,6 +295,10 @@ def main(): | |||||
| from optparse import OptionParser | from optparse import OptionParser | ||||
| parser = OptionParser() | parser = OptionParser() | ||||
| parser.add_option('-a', action='append', dest='add', | |||||
| default=[], help='add the arg as metadata for files, tag=value') | |||||
| parser.add_option('-d', action='append', dest='delete', | |||||
| default=[], help='delete the arg as metadata from files. Either specify tag, and all tags are removed, or specify tag=value and that specific tag will be removed.') | |||||
| parser.add_option('-l', action='store_true', dest='list', | parser.add_option('-l', action='store_true', dest='list', | ||||
| default=False, help='list metadata') | default=False, help='list metadata') | ||||
| @@ -299,12 +309,34 @@ def main(): | |||||
| #print >>sys.stderr, `storefname` | #print >>sys.stderr, `storefname` | ||||
| objstr = ObjectStore.load(storefname) | objstr = ObjectStore.load(storefname) | ||||
| for i in args: | |||||
| for j in objstr.by_file(i): | |||||
| for k, v in _iterdictlist(j): | |||||
| print '%s:\t%s' % (k, v) | |||||
| #objstr.store() | |||||
| if options.list: | |||||
| for i in args: | |||||
| for j in objstr.by_file(i): | |||||
| #print >>sys.stderr, `j._obj` | |||||
| for k, v in _iterdictlist(j): | |||||
| print '%s:\t%s' % (k, v) | |||||
| elif options.add: | |||||
| addprops = map(lambda x: x.split('=', 1), options.add) | |||||
| for i in args: | |||||
| for j in objstr.by_file(i): | |||||
| nobj = j.new_version(*addprops) | |||||
| objstr.loadobj(nobj) | |||||
| elif options.delete: | |||||
| for i in args: | |||||
| for j in objstr.by_file(i): | |||||
| obj = j.__to_dict__() | |||||
| for k in options.delete: | |||||
| try: | |||||
| key, v = k.split('=', 1) | |||||
| obj[key].remove(v) | |||||
| except ValueError: | |||||
| del obj[k] | |||||
| nobj = MDBase(obj) | |||||
| objstr.loadobj(nobj) | |||||
| else: | |||||
| raise NotImplementedError | |||||
| objstr.store(storefname) | |||||
| if __name__ == '__main__': # pragma: no cover | if __name__ == '__main__': # pragma: no cover | ||||
| main() | main() | ||||
| @@ -455,8 +487,9 @@ class _TestCases(unittest.TestCase): | |||||
| import sys | import sys | ||||
| import StringIO | import StringIO | ||||
| import itertools | |||||
| with mock.patch('os.path.expanduser', side_effect=(storefname, )) \ | |||||
| with mock.patch('os.path.expanduser', side_effect=itertools.repeat(storefname)) \ | |||||
| as eu: | as eu: | ||||
| with nested(mock.patch('sys.stdout', | with nested(mock.patch('sys.stdout', | ||||
| StringIO.StringIO()), mock.patch('sys.argv', | StringIO.StringIO()), mock.patch('sys.argv', | ||||
| @@ -466,8 +499,50 @@ class _TestCases(unittest.TestCase): | |||||
| 'dc:creator:\tJohn-Mark Gurney\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n') | 'dc:creator:\tJohn-Mark Gurney\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n') | ||||
| eu.assert_called_with('~/.medashare_store.pasn1') | eu.assert_called_with('~/.medashare_store.pasn1') | ||||
| if False: # pragma: no cover | |||||
| # Example how to force proper output | |||||
| with mock.patch('sys.stdout', StringIO.StringIO()) as ssw: | |||||
| print 'foobar' | |||||
| self.assertEqual(ssw.getvalue(), 'foobar\n') | |||||
| with nested(mock.patch('sys.stdout', | |||||
| StringIO.StringIO()), mock.patch('sys.argv', | |||||
| [ 'progname', '-a', 'dc:creator=Another user', '-a', 'foo=bar=baz', testfname ])) as (stdout, argv): | |||||
| main() | |||||
| with nested(mock.patch('sys.stdout', | |||||
| StringIO.StringIO()), mock.patch('sys.argv', | |||||
| [ 'progname', '-l', testfname ])) as (stdout, argv): | |||||
| main() | |||||
| self.assertEqual(stdout.getvalue(), | |||||
| 'dc:creator:\tAnother user\ndc:creator:\tJohn-Mark Gurney\nfoo:\tbar=baz\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n') | |||||
| with nested(mock.patch('sys.stdout', | |||||
| StringIO.StringIO()), mock.patch('sys.argv', | |||||
| [ 'progname', '-d', 'dc:creator', testfname ])) as (stdout, argv): | |||||
| main() | |||||
| with nested(mock.patch('sys.stdout', | |||||
| StringIO.StringIO()), mock.patch('sys.argv', | |||||
| [ 'progname', '-l', testfname ])) as (stdout, argv): | |||||
| main() | |||||
| self.assertEqual(stdout.getvalue(), | |||||
| 'foo:\tbar=baz\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n') | |||||
| with nested(mock.patch('sys.stdout', | |||||
| StringIO.StringIO()), mock.patch('sys.argv', | |||||
| [ 'progname', '-a', 'foo=bleh', testfname ])) as (stdout, argv): | |||||
| main() | |||||
| with nested(mock.patch('sys.stdout', | |||||
| StringIO.StringIO()), mock.patch('sys.argv', | |||||
| [ 'progname', '-l', testfname ])) as (stdout, argv): | |||||
| main() | |||||
| self.assertEqual(stdout.getvalue(), | |||||
| 'foo:\tbar=baz\nfoo:\tbleh\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n') | |||||
| with nested(mock.patch('sys.stdout', | |||||
| StringIO.StringIO()), mock.patch('sys.argv', | |||||
| [ 'progname', '-d', 'foo=bar=baz', testfname ])) as (stdout, argv): | |||||
| main() | |||||
| with nested(mock.patch('sys.stdout', | |||||
| StringIO.StringIO()), mock.patch('sys.argv', | |||||
| [ 'progname', '-l', testfname ])) as (stdout, argv): | |||||
| main() | |||||
| self.assertEqual(stdout.getvalue(), | |||||
| 'foo:\tbleh\nhashes:\tsha256:91751cee0a1ab8414400238a761411daa29643ab4b8243e9a91649e25be53ada\nhashes:\tsha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f\nlang:\ten\n') | |||||