@@ -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')