add version support
[pithos] / tools / store
index fefbc5f..584273c 100755 (executable)
@@ -5,10 +5,13 @@ from optparse import OptionParser
 from os.path import basename
 from sys import argv, exit, stdin, stdout
 from pithos.lib.client import Client, Fault
+from datetime import datetime
 
 import json
 import logging
 import types
+import re
+import time as _time
 
 DEFAULT_HOST = 'pithos.dev.grnet.gr'
 DEFAULT_API = 'v1'
@@ -54,7 +57,7 @@ class Command(object):
         
         self.parser = parser
         self.args = args
-    
+
     def add_options(self, parser):
         pass
 
@@ -92,17 +95,26 @@ class List(Command):
         parser.add_option('--if-unmodified-since', action='store', type='str',
                           dest='if_unmodified_since', default=None,
                           help='show output if not modified since then')
+        parser.add_option('--until', action='store', dest='until',
+                          default=False, help='show metadata until that date')
+        parser.add_option('--format', action='store', dest='format',
+                          default='%d/%m/%Y', help='format to parse until date')
 
     def execute(self, container=None):
         if container:
             self.list_objects(container)
         else:
             self.list_containers()
-    
+
     def list_containers(self):
         params = {'limit':self.limit, 'marker':self.marker}
         headers = {'IF_MODIFIED_SINCE':self.if_modified_since,
                    'IF_UNMODIFIED_SINCE':self.if_unmodified_since}
+        
+        if self.until:
+            t = _time.strptime(self.until, self.format)
+            params['until'] = int(_time.mktime(t))
+        
         l = self.client.list_containers(self.detail, params, headers)
         print_list(l)
 
@@ -112,6 +124,15 @@ class List(Command):
                   'path':self.path, 'meta':self.meta}
         headers = {'IF_MODIFIED_SINCE':self.if_modified_since,
                    'IF_UNMODIFIED_SINCE':self.if_unmodified_since}
+        container, sep, object = container.partition('/')
+        if object:
+            print '%s/%s is an object' %(container, object)
+            return
+        
+        if self.until:
+            t = _time.strptime(self.until, self.format)
+            params['until'] = int(_time.mktime(t))
+        
         l = self.client.list_objects(container, self.detail, params, headers)
         print_list(l)
 
@@ -123,17 +144,29 @@ class Meta(Command):
     def add_options(self, parser):
         parser.add_option('-r', action='store_true', dest='restricted',
                           default=False, help='show only user defined metadata')
+        parser.add_option('--until', action='store', dest='until',
+                          default=False, help='show metadata until that date')
+        parser.add_option('--format', action='store', dest='format',
+                          default='%d/%m/%Y', help='format to parse until date')
+        parser.add_option('--version', action='store', dest='version',
+                          default=None, help='show specific version \
+                                  (applies only for objects)')
 
     def execute(self, path=''):
         container, sep, object = path.partition('/')
+        if self.until:
+            t = _time.strptime(self.until, self.format)
+            self.until = int(_time.mktime(t))
         if object:
             meta = self.client.retrieve_object_metadata(container, object,
-                                                        self.restricted)
+                                                        self.restricted,
+                                                        self.version)
         elif container:
             meta = self.client.retrieve_container_metadata(container,
-                                                           self.restricted)
+                                                           self.restricted,
+                                                           self.until)
         else:
-            meta = self.client.account_metadata(self.restricted)
+            meta = self.client.account_metadata(self.restricted, self.until)
         if meta == None:
             print 'Entity does not exist'
         else:
@@ -189,6 +222,11 @@ class GetObject(Command):
         parser.add_option('-f', action='store', type='str',
                           dest='file', default=None,
                           help='save output in file')
+        parser.add_option('--version', action='store', type='str',
+                          dest='version', default='list',
+                          help='if \'list\' and in detailed mode get object\'s \
+                               full version list otherwise get the specific \
+                               version')
 
     def execute(self, path):
         headers = {}
@@ -201,18 +239,17 @@ class GetObject(Command):
             headers[a.replace('-', '_').upper()] = getattr(self, a)
         container, sep, object = path.partition('/')
         data = self.client.retrieve_object(container, object, self.detail,
-                                          headers)
-        if self.file:
-            if self.detail:
-                f = self.file and open(self.file, 'w') or stdout
-                data = json.loads(data)
-                print_dict(data, f=f)
+                                          headers, self.version)
+        f = self.file and open(self.file, 'w') or stdout
+        if self.detail:
+            data = json.loads(data)
+            if self.version == 'list':
+                print_versions(data, f=f)
             else:
-                fw = open(self.file, 'w')
-                fw.write(data)
-                fw.close()
+                print_dict(data, f=f)
         else:
-            print data
+            f.write(data)
+        f.close()
 
 @cli_command('put')
 class PutObject(Command):
@@ -233,8 +270,11 @@ class PutObject(Command):
         parser.add_option('--manifest', action='store', type='str',
                           dest='manifest', default=None,
                           help='use for large file support')
+        parser.add_option('--touch', action='store_true',
+                          dest='touch', default=False,
+                          help='create object with zero data')
 
-    def execute(self, path, srcpath, *args):
+    def execute(self, path, srcpath='-', *args):
         headers = {}
         if self.manifest:
             headers['X_OBJECT_MANIFEST'] = self.manifest
@@ -251,11 +291,15 @@ class PutObject(Command):
         
         container, sep, object = path.partition('/')
         
-        f = srcpath != '-' and open(srcpath) or stdin
-        chunked = (self.chunked or f == stdin) and True or False
+        f = None
+        chunked = False
+        if not self.touch:
+            f = srcpath != '-' and open(srcpath) or stdin
+            chunked = (self.chunked or f == stdin) and True or False
         self.client.create_object(container, object, f, chunked=chunked,
                                   headers=headers)
-        f.close()
+        if f:
+            f.close()
 
 @cli_command('copy', 'cp')
 class CopyObject(Command):
@@ -298,11 +342,11 @@ class SetMeta(Command):
 @cli_command('update')
 class UpdateObject(Command):
     syntax = '<container>/<object> path [key=val] [...]'
-    description = 'update object metadata/data'
+    description = 'update object metadata/data (default mode: append)'
     
     def add_options(self, parser):
         parser.add_option('-a', action='store_true', dest='append',
-                          default=None, help='append data')
+                          default=True, help='append data')
         parser.add_option('--start', action='store',
                           dest='start',
                           default=None, help='range of data to be updated')
@@ -320,7 +364,7 @@ class UpdateObject(Command):
                           dest='manifest', default=None,
                           help='use for large file support')
 
-    def execute(self, path, srcpath, *args):
+    def execute(self, path, srcpath='-', *args):
         headers = {}
         if self.manifest:
             headers['X_OBJECT_MANIFEST'] = self.manifest
@@ -379,8 +423,15 @@ def print_dict(d, header='name', f=stdout):
     header = header in d and header or 'subdir'
     if header and header in d:
         f.write('%s\n' %d.pop(header))
+    patterns = ['^x_(account|container|object)_meta_(\w+)$']
+    patterns.append(patterns[0].replace('_', '-'))
     for key, val in sorted(d.items()):
-        f.write('%s: %s\n' % (key.rjust(15), val))
+        for p in patterns:
+            p = re.compile(p)
+            m = p.match(key)
+            if m:
+                key = m.group(2)
+        f.write('%s: %s\n' % (key.rjust(30), val))
 
 def print_list(l, verbose=False, f=stdout):
     for elem in l:
@@ -396,6 +447,14 @@ def print_list(l, verbose=False, f=stdout):
         else:
             f.write('%s\n' % elem)
 
+def print_versions(data, f=stdout):
+    if 'versions' not in data:
+        f.write('%s\n' %data)
+        return
+    f.write('versions:\n')
+    for id, t in data['versions']:
+        f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(t)))
+
 def main():
     try:
         name = argv[1]
@@ -408,12 +467,13 @@ def main():
     
     try:
         cmd.execute(*cmd.args)
-    except TypeError:
+    except TypeError, e:
+        print e
         cmd.parser.usage = '%%prog %s [options] %s' % (name, cmd.syntax)
         cmd.parser.print_help()
         exit(1)
     except Fault, f:
-        print f.data
+        print f.status, f.data
 
 if __name__ == '__main__':
     main()