| @@ -1,5 +1,7 @@ | |||
| #!/usr/bin/env python | |||
| #import pdb, sys; mypdb = pdb.Pdb(stdout=sys.stderr); mypdb.set_trace() | |||
| import copy | |||
| import datetime | |||
| import hashlib | |||
| @@ -26,6 +28,8 @@ def _iterdictlist(obj): | |||
| itms.sort() | |||
| for k, v in itms: | |||
| if isinstance(v, list): | |||
| v = v[:] | |||
| v.sort() | |||
| for i in v: | |||
| yield k, i | |||
| else: | |||
| @@ -78,8 +82,8 @@ class MDBase(object): | |||
| `ty`) | |||
| 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) | |||
| @@ -113,10 +117,12 @@ class MetaData(MDBase): | |||
| _type = 'metadata' | |||
| def _trytodict(o): | |||
| if isinstance(o, uuid.UUID): | |||
| return 'unicode', str(o) | |||
| try: | |||
| return 'dict', o.__to_dict__() | |||
| 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) | |||
| @@ -271,7 +277,7 @@ class FileObject(MDBase): | |||
| 'id': cls.make_id(filename), | |||
| 'mtime': datetime.datetime.utcfromtimestamp(s.st_mtime), | |||
| 'size': s.st_size, | |||
| 'hashes': ( _hashfile(filename), ), | |||
| 'hashes': [ _hashfile(filename), ], | |||
| } | |||
| @@ -289,6 +295,10 @@ def main(): | |||
| from optparse import 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', | |||
| default=False, help='list metadata') | |||
| @@ -299,12 +309,34 @@ def main(): | |||
| #print >>sys.stderr, `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 | |||
| main() | |||
| @@ -455,8 +487,9 @@ class _TestCases(unittest.TestCase): | |||
| import sys | |||
| 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: | |||
| with nested(mock.patch('sys.stdout', | |||
| 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') | |||
| 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') | |||