Re-implement store-copy semantics (feature #3310)
authorStavros Sachtouris <saxtouri@admin.grnet.gr>
Mon, 25 Feb 2013 16:05:04 +0000 (18:05 +0200)
committerStavros Sachtouris <saxtouri@admin.grnet.gr>
Mon, 25 Feb 2013 16:05:04 +0000 (18:05 +0200)
kamaki/cli/commands/pithos_cli.py
version

index 5592ca5..3e95407 100644 (file)
@@ -222,8 +222,20 @@ class _store_container_command(_store_account_command):
     def _dest_container_path(self, dest_container_path):
         if self['destination_container']:
             return (self['destination_container'], dest_container_path)
-        dst = dest_container_path.split(':')
-        return (dst[0], dst[1]) if len(dst) > 1 else (None, dst[0])
+        if dest_container_path:
+            dst = dest_container_path.split(':')
+            if len(dst) > 1:
+                try:
+                    self.client.container = dst[0]
+                    self.client.get_container_info(dst[0])
+                except ClientError as err:
+                    if err.status in (404, 204):
+                        raiseCLIError(
+                            'Destination container %s not found' % dst[0])
+                    raise
+                return (dst[0], dst[1])
+            return(None, dst[0])
+        raiseCLIError('No destination container:path provided')
 
     def extract_container_and_path(
             self,
@@ -518,17 +530,12 @@ class store_create(_store_container_command):
 class store_copy(_store_container_command):
     """Copy objects from container to (another) container
     Semantics:
-    copy cont:path path2
-    .   will copy all <obj> prefixed with path, as path2<obj>
-    .   or as path2 if path corresponds to just one whole object
+    copy cont:path dir
+    .   transfer path as dir/path
     copy cont:path cont2:
-    .   will copy all <obj> prefixed with path to container cont2
-    copy cont:path [cont2:]path2 --exact-match
-    .   will copy at most one <obj> as a new object named path2,
-    .   provided path corresponds to a whole object path
-    copy cont:path [cont2:]path2 --replace
-    .   will copy all <obj> prefixed with path, replacing path with path2
-    where <obj> is a full file or directory object path.
+    .   trasnfer all <obj> prefixed with path to container cont2
+    copy cont:path [cont2:]path2
+    .   transfer path to path2
     Use options:
     1. <container1>:<path1> [container2:]<path2> : if container2 is not given,
     destination is container1:path2
@@ -550,111 +557,130 @@ class store_copy(_store_container_command):
         recursive=FlagArgument(
             'copy directory and contents',
             ('-r', '--recursive')),
-        src_prefix=ValueArgument(
-            'Prefix of source objects (similar to prefix*)',
-            '--prefix',
+        prefix=FlagArgument(
+            'Match objects prefixed with src path (feels like src_path*)',
+            '--with-prefix',
             default=''),
-        src_suffix=ValueArgument(
-            'Suffix of source objects (similar to *suffix)',
-            '--suffix',
+        suffix=ValueArgument(
+            'Suffix of source objects (feels like *suffix)',
+            '--with-suffix',
             default=''),
-        dst_prefix=ValueArgument(
-            'Prefix all destination objects with dst path',
-            '--prefix-dst',
+        add_prefix=ValueArgument('Prefix targets', '--add-prefix', default=''),
+        add_suffix=ValueArgument('Suffix targets', '--add-suffix', default=''),
+        prefix_replace=ValueArgument(
+            'Prefix of src to replace with dst path + add_prefix, if matched',
+            '--prefix-to-replace',
             default=''),
-        dst_suffix=ValueArgument(
-            'Suffix all destination objects with dst path',
-            '--suffix-dst',
-            default=''),
-        replace=ValueArgument(
-            'Set the part of src path to replace with dst path',
-            '--replace')
+        suffix_replace=ValueArgument(
+            'Suffix of src to replace with add_suffix, if matched',
+            '--suffix-to-replace',
+            default='')
     )
 
     def _get_all(self, prefix):
         return self.client.container_get(prefix=prefix).json
 
-    def _objlist(self, src_cnt, src_path, dst_cnt, dst_path):
+    def _get_src_objects(self, src_cnt, src_path):
+        """Get a list of the source objects to be called
+
+        :param src_cnt: (str) source container
+
+        :param src_path: (str) source path
+
+        :returns: (method, params) a method that returns a list when called
+        or (object) if it is a single object
+        """
         if src_path and src_path[-1] == '/':
             src_path = src_path[:-1]
         self.client.container = src_cnt
-        (src_list_foo, src_list_args) = (None, {})
 
+        if self['prefix']:
+            return (self._get_all, dict(prefix=src_path))
         try:
-            list_all = False
             srcobj = self.client.get_object_info(src_path)
         except ClientError as srcerr:
-            if srcerr not in (404, 204):
+            if srcerr.status == 404:
+                raiseCLIError(
+                    'Source object %s not in cont. %s' % (src_path, src_cnt),
+                    details=['Hint: --with-prefix to match multiple objects'])
+            elif srcerr.status not in (204,):
                 raise
-            src_list_foo = self.client.list_objects
-            list_all = True
-        else:
-            if self._is_dir(srcobj):
-                if not self['recursive']:
-                    raiseCLIError(
-                        'Object %s of cont. %s is a dir' % (
-                            src_path,
-                            src_cnt),
-                        details=['Use --recursive to access directories'])
-                self['src_prefix'] = self.path
-        finally:
-            if self['src_prefix']:
-                if not self.path.startswith(self['src_prefix']):
-                    raiseCLIError('Path %s conflicts with prefix %s' % (
-                            self.path,
-                            self['src_prefix']))
-                src_list_foo = self._get_all,
-                src_list_args = dict(prefix=self['src_prefix'])
-            elif list_all:
-                src_list_foo = self.client.list_objects
-
-        if dst_path and dst_path[-1] == '/':
+            return (self.client.list_objects, {})
+        if self._is_dir(srcobj):
+            if not self['recursive']:
+                raiseCLIError(
+                    'Object %s of cont. %s is a dir' % (src_path, src_cnt),
+                    details=['Use --recursive to access directories'])
+            return (self._get_all, dict(prefix=src_path))
+        srcobj['name'] = src_path
+        return srcobj
+
+    def _objlist(self, dst_cont, dst_path):
+        src_iter = self._get_src_objects(self.container, self.path)
+        src_N = isinstance(src_iter, tuple)
+        add_prefix = self['add_prefix'].strip('/')
+
+        if dst_path and dst_path.endswith('/'):
             dst_path = dst_path[:-1]
-        if src_list_foo:
-            #  N src objects
-            (p, s) = (self['dst_prefix'], self['dst_suffix'])
-            if p or s:
-                for obj in src_list_foo(**src_list_args):
-                    name = obj['name']
-                    if not name.endswith(self['src_suffix']):
-                        continue
-                    name = '%s%s%s' % (
-                        self['dst_prefix'],
-                        name,
-                        self['dst_suffix'])
-        else:
-            pass
-            #  1 src object
 
+        self.client.container = dst_cont
+        try:
+            dstobj = self.client.get_object_info(dst_path)
+        except ClientError as trgerr:
+            if trgerr.status in (404,):
+                if src_N:
+                    raiseCLIError(
+                        'Cannot merge multiple paths to path %s' % dst_path,
+                        details=[
+                            'Try to use / or a directory as destination',
+                            'or create the destination dir (/store mkdir)',
+                            'or use a single object as source'])
+            elif trgerr.status not in (204,):
+                raise
+        else:
+            if self._is_dir(dstobj):
+                add_prefix = '%s/%s' % (dst_path.strip('/'), add_prefix)
+            elif src_N:
+                raiseCLIError(
+                    'Cannot merge multiple paths to path' % dst_path,
+                    details=[
+                        'Try to use / or a directory as destination',
+                        'or create the destination dir (/store mkdir)',
+                        'or use a single object as source'])
+
+        self.client.container = self.container
+        if src_N:
+            (method, kwargs) = src_iter
+            for obj in method(**kwargs):
+                name = obj['name']
+                if name.endswith(self['suffix']):
+                    yield (name, self._get_new_object(name, add_prefix))
+        elif src_iter['name'].endswith(self['suffix']):
+            name = src_iter['name']
+            yield (name, self._get_new_object(dst_path or name, add_prefix))
+        else:
+            raiseCLIError('Source path %s conflicts with suffix %s' % (
+                src_iter['name'],
+                self['suffix']))
 
-    """
-    def _objlist(self, dst_path):
-        if self['exact_match']:
-            return [(dst_path or self.path, self.path)]
-        r = self.client.container_get(prefix=self.path)
-        if len(r.json) == 1:
-            obj = r.json[0]
-            return [(obj['name'], dst_path or obj['name'])]
-        start = len(self.path) if self['replace'] else 0
-        return [(obj['name'], '%s%s' % (
-            dst_path,
-            obj['name'][start:])) for obj in r.json]
-    """
+    def _get_new_object(self, obj, add_prefix):
+        if self['prefix_replace'] and obj.startswith(self['prefix_replace']):
+            obj = obj[len(self['prefix_replace']):]
+        if self['suffix_replace'] and obj.endswith(self['suffix_replace']):
+            obj = obj[:-len(self['suffix_replace'])]
+        return add_prefix + obj + self['add_suffix']
 
     @errors.generic.all
     @errors.pithos.connection
     @errors.pithos.container
     def _run(self, dst_cont, dst_path):
-        for lala in self._src_objlist(self.container, self.path):
-            print('I HAVE %s : %s !' % lala)
-        return
         no_source_object = True
-        for src_object, dst_object in self._objlist(dst_path):
+        for src_object, dst_object in self._objlist(dst_cont, dst_path):
             no_source_object = False
             self.client.copy_object(
                 src_container=self.container,
                 src_object=src_object,
-                dst_container=dst_cont or self.container,
+                dst_container=dst_cont,
                 dst_object=dst_object,
                 source_version=self['source_version'],
                 public=self['public'],
@@ -673,7 +699,7 @@ class store_copy(_store_container_command):
             path_is_optional=False)
         (dst_cont, dst_path) = self._dest_container_path(
             destination_container___path)
-        self._run(dst_cont=dst_cont, dst_path=dst_path or '')
+        self._run(dst_cont=dst_cont or self.container, dst_path=dst_path or '')
 
 
 @command(pithos_cmds)
diff --git a/version b/version
index de31d70..c17c572 100644 (file)
--- a/version
+++ b/version
@@ -1 +1 @@
-0.8next
+0.7next