Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 850685bf

History | View | Annotate | Download (44.7 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.command
33

    
34
from kamaki.cli import command
35
from kamaki.cli.command_tree import CommandTree
36
from kamaki.cli.errors import raiseCLIError, CLISyntaxError
37
from kamaki.cli.utils import format_size, print_dict, pretty_keys
38
from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
39
from kamaki.cli.argument import KeyValueArgument, DateArgument
40
from kamaki.cli.argument import ProgressBarArgument
41
from kamaki.cli.commands import _command_init
42
from kamaki.clients.pithos import PithosClient, ClientError
43
from kamaki.cli.utils import bold
44
from sys import stdout
45
from time import localtime, strftime
46
from logging import getLogger
47

    
48
kloger = getLogger('kamaki')
49

    
50
pithos_cmds = CommandTree('store', 'Pithos+ storage commands')
51
_commands = [pithos_cmds]
52

    
53

    
54
# Argument functionality
55

    
56

    
57
class DelimiterArgument(ValueArgument):
58
    """
59
    :value type: string
60
    :value returns: given string or /
61
    """
62

    
63
    def __init__(self, caller_obj, help='', parsed_name=None, default=None):
64
        super(DelimiterArgument, self).__init__(help, parsed_name, default)
65
        self.caller_obj = caller_obj
66

    
67
    @property
68
    def value(self):
69
        if self.caller_obj['recursive']:
70
            return '/'
71
        return getattr(self, '_value', self.default)
72

    
73
    @value.setter
74
    def value(self, newvalue):
75
        self._value = newvalue
76

    
77

    
78
class SharingArgument(ValueArgument):
79
    """Set sharing (read and/or write) groups
80

81
    :value type: "read=term1,term2,... write=term1,term2,..."
82

83
    :value returns: {'read':['term1', 'term2', ...],
84
        'write':['term1', 'term2', ...]}
85
    """
86

    
87
    @property
88
    def value(self):
89
        return getattr(self, '_value', self.default)
90

    
91
    @value.setter
92
    def value(self, newvalue):
93
        perms = {}
94
        try:
95
            permlist = newvalue.split(' ')
96
        except AttributeError:
97
            return
98
        for p in permlist:
99
            try:
100
                (key, val) = p.split('=')
101
            except ValueError as err:
102
                raiseCLIError(err, 'Error in --sharing',
103
                    details='Incorrect format',
104
                    importance=1)
105
            if key.lower() not in ('read', 'write'):
106
                raiseCLIError(err, 'Error in --sharing',
107
                    details='Invalid permission key %s' % key,
108
                    importance=1)
109
            val_list = val.split(',')
110
            if not key in perms:
111
                perms[key] = []
112
            for item in val_list:
113
                if item not in perms[key]:
114
                    perms[key].append(item)
115
        self._value = perms
116

    
117

    
118
class RangeArgument(ValueArgument):
119
    """
120
    :value type: string of the form <start>-<end>
121
        where <start> and <end> are integers
122

123
    :value returns: the input string, after type checking <start> and <end>
124
    """
125

    
126
    @property
127
    def value(self):
128
        return getattr(self, '_value', self.default)
129

    
130
    @value.setter
131
    def value(self, newvalue):
132
        if newvalue is None:
133
            self._value = self.default
134
            return
135
        (start, end) = newvalue.split('-')
136
        (start, end) = (int(start), int(end))
137
        self._value = '%s-%s' % (start, end)
138

    
139
# Command specs
140

    
141

    
142
class _pithos_init(_command_init):
143
    """Initialize a pithos+ kamaki client"""
144

    
145
    def main(self):
146
        self.token = self.config.get('store', 'token')\
147
            or self.config.get('global', 'token')
148
        self.base_url = self.config.get('store', 'url')\
149
            or self.config.get('global', 'url')
150
        self.account = self.config.get('store', 'account')\
151
            or self.config.get('global', 'account')
152
        self.container = self.config.get('store', 'container')\
153
            or self.config.get('global', 'container')
154
        self.client = PithosClient(base_url=self.base_url,
155
            token=self.token,
156
            account=self.account,
157
            container=self.container)
158

    
159

    
160
class _store_account_command(_pithos_init):
161
    """Base class for account level storage commands"""
162

    
163
    def __init__(self, arguments={}):
164
        super(_store_account_command, self).__init__(arguments)
165
        self['account']=ValueArgument(
166
            'Set user account (not permanent)',
167
            '--account')
168

    
169
    def main(self):
170
        super(_store_account_command, self).main()
171
        if self['account']:
172
            self.client.account = self['account']
173

    
174

    
175
class _store_container_command(_store_account_command):
176
    """Base class for container level storage commands"""
177

    
178
    generic_err_details = ['Choose one of the following options:',
179
    '  1. Set store.container variable (permanent)',
180
    '     /config set store.container <container>',
181
    '  2. --container=<container> (temporary, overrides 1)',
182
    '  3. Use <container>:<path> (temporary, overrides all)']
183
    container = None
184
    path = None
185

    
186
    def __init__(self, arguments={}):
187
        super(_store_container_command, self).__init__(arguments)
188
        self['container']=ValueArgument(
189
            'Set container to work with (temporary)',
190
            '--container')
191

    
192
    def extract_container_and_path(self,
193
        container_with_path,
194
        path_is_optional=True):
195
        try:
196
            assert isinstance(container_with_path, str)
197
        except AssertionError as err:
198
            raiseCLIError(err)
199

    
200
        cont, sep, path = container_with_path.partition(':')
201

    
202
        if sep:
203
            if not cont:
204
                raiseCLIError(CLISyntaxError('Container is missing\n',
205
                    details=self.generic_err_details))
206
            alt_cont = self['container']
207
            if alt_cont and cont != alt_cont:
208
                raiseCLIError(CLISyntaxError(
209
                    'Conflict: 2 containers (%s, %s)' % (cont, alt_cont),
210
                    details=self.generic_err_details)
211
                )
212
            self.container = cont
213
            if not path:
214
                raiseCLIError(CLISyntaxError(
215
                    'Path is missing for object in container %s' % cont,
216
                    details=self.generic_err_details)
217
                )
218
            self.path = path
219
        else:
220
            alt_cont = self['container'] or self.client.container
221
            if alt_cont:
222
                self.container = alt_cont
223
                self.path = cont
224
            elif path_is_optional:
225
                self.container = cont
226
                self.path = None
227
            else:
228
                self.container = cont
229
                raiseCLIError(CLISyntaxError(
230
                    'Both container and path are required',
231
                    details=self.generic_err_details)
232
                )
233

    
234
    def main(self, container_with_path=None, path_is_optional=True):
235
        super(_store_container_command, self).main()
236
        if container_with_path is not None:
237
            self.extract_container_and_path(
238
                container_with_path,
239
                path_is_optional)
240
            self.client.container = self.container
241
        elif self['container']:
242
            self.client.container = self['container']
243
        self.container = self.client.container
244

    
245

    
246
@command(pithos_cmds)
247
class store_list(_store_container_command):
248
    """List containers, object trees or objects in a directory
249
    Use with:
250
    1 no parameters : containers in set account
251
    2. one parameter (container) or --container : contents of container
252
    3. <container>:<prefix> or --container=<container> <prefix>: objects in
253
        container starting with prefix
254
    """
255

    
256
    arguments = dict(
257
        detail=FlagArgument('show detailed output', '-l'),
258
        show_size=ValueArgument('print output in chunks of size N', '-N'),
259
        limit=IntArgument('show limited output', '-n'),
260
        marker=ValueArgument('show output greater that marker', '--marker'),
261
        prefix=ValueArgument('show output staritng with prefix', '--prefix'),
262
        delimiter=ValueArgument('show output up to delimiter', '--delimiter'),
263
        path=ValueArgument(
264
            'show output starting with prefix up to /',
265
            '--path'),
266
        meta=ValueArgument(
267
            'show output with specified meta keys',
268
            '--meta',
269
            default=[]),
270
        if_modified_since=ValueArgument(
271
            'show output modified since then',
272
            '--if-modified-since'),
273
        if_unmodified_since=ValueArgument(
274
            'show output not modified since then',
275
            '--if-unmodified-since'),
276
        until=DateArgument('show metadata until then', '--until'),
277
        format=ValueArgument(
278
            'format to parse until data (default: d/m/Y H:M:S )',
279
            '--format'),
280
        shared=FlagArgument('show only shared', '--shared'),
281
        public=FlagArgument('show only public', '--public'),
282
    )
283

    
284
    def print_objects(self, object_list):
285
        import sys
286
        try:
287
            limit = int(self['show_size'])
288
        except (AttributeError, TypeError):
289
            limit = len(object_list) + 1
290
        for index, obj in enumerate(object_list):
291
            if 'content_type' not in obj:
292
                continue
293
            pretty_obj = obj.copy()
294
            index += 1
295
            empty_space = ' ' * (len(str(len(object_list))) - len(str(index)))
296
            if obj['content_type'] == 'application/directory':
297
                isDir = True
298
                size = 'D'
299
            else:
300
                isDir = False
301
                size = format_size(obj['bytes'])
302
                pretty_obj['bytes'] = '%s (%s)' % (obj['bytes'], size)
303
            oname = bold(obj['name'])
304
            if self['detail']:
305
                print('%s%s. %s' % (empty_space, index, oname))
306
                print_dict(pretty_keys(pretty_obj), exclude=('name'))
307
                print
308
            else:
309
                oname = '%s%s. %6s %s' % (empty_space, index, size, oname)
310
                oname += '/' if isDir else ''
311
                print(oname)
312
            if limit <= index < len(object_list) and index % limit == 0:
313
                print('(press "enter" to continue)')
314
                sys.stdin.read(1)
315

    
316
    def print_containers(self, container_list):
317
        import sys
318
        try:
319
            limit = self['show_size']
320
            limit = int(limit)
321
        except (AttributeError, TypeError):
322
            limit = len(container_list) + 1
323
        for index, container in enumerate(container_list):
324
            if 'bytes' in container:
325
                size = format_size(container['bytes'])
326
            cname = '%s. %s' % (index + 1, bold(container['name']))
327
            if self['detail']:
328
                print(cname)
329
                pretty_c = container.copy()
330
                if 'bytes' in container:
331
                    pretty_c['bytes'] = '%s (%s)' % (container['bytes'], size)
332
                print_dict(pretty_keys(pretty_c), exclude=('name'))
333
                print
334
            else:
335
                if 'count' in container and 'bytes' in container:
336
                    print('%s (%s, %s objects)'\
337
                    % (cname, size, container['count']))
338
                else:
339
                    print(cname)
340
            if limit <= index < len(container_list) and index % limit == 0:
341
                print('(press "enter" to continue)')
342
                sys.stdin.read(1)
343

    
344
    def main(self, container____path__=None):
345
        super(self.__class__, self).main(container____path__)
346
        try:
347
            if self.container is None:
348
                r = self.client.account_get(
349
                    limit=self['limit'],
350
                    marker=self['marker'],
351
                    if_modified_since=self['if_modified_since'],
352
                    if_unmodified_since=self['if_unmodified_since'],
353
                    until=self['until'],
354
                    show_only_shared=self['shared'])
355
                self.print_containers(r.json)
356
            else:
357
                prefix = self.path if self.path\
358
                else self['prefix']
359
                r = self.client.container_get(
360
                    limit=self['limit'],
361
                    marker=self['marker'],
362
                    prefix=prefix,
363
                    delimiter=self['delimiter'],
364
                    path=self['path'],
365
                    if_modified_since=self['if_modified_since'],
366
                    if_unmodified_since=self['if_unmodified_since'],
367
                    until=self['until'],
368
                    meta=self['meta'],
369
                    show_only_shared=self['shared'])
370
                self.print_objects(r.json)
371
        except ClientError as err:
372
            if err.status == 404:
373
                if 'Container does not exist' in ('%s' % err):
374
                    raiseCLIError(
375
                        err,
376
                        'No container %s in account %s'\
377
                        % (self.container, self.account),
378
                        details=self.generic_err_details)
379
                elif 'Object does not exist' in ('%s' % err):
380
                    raiseCLIError(
381
                        err,
382
                        'No object %s in %s\'s container %s'\
383
                        % (self.path, self.account, self.container),
384
                        details=self.generic_err_details)
385
            raiseCLIError(err)
386

    
387

    
388
@command(pithos_cmds)
389
class store_mkdir(_store_container_command):
390
    """Create a directory"""
391

    
392
    def main(self, container___directory):
393
        super(self.__class__,
394
            self).main(container___directory, path_is_optional=False)
395
        try:
396
            self.client.create_directory(self.path)
397
        except ClientError as err:
398
            raiseCLIError(err)
399

    
400

    
401
@command(pithos_cmds)
402
class store_create(_store_container_command):
403
    """Create a container or a directory object"""
404

    
405
    arguments = dict(
406
        versioning=ValueArgument(
407
            'set container versioning (auto/none)',
408
            '--versioning'),
409
        quota=IntArgument('set default container quota', '--quota'),
410
        meta=KeyValueArgument(
411
            'set container metadata (can be repeated)',
412
            '--meta')
413
    )
414

    
415
    def main(self, container____directory__):
416
        super(self.__class__, self).main(container____directory__)
417
        try:
418
            if self.path is None:
419
                self.client.container_put(quota=self['quota'],
420
                    versioning=self['versioning'],
421
                    metadata=self['meta'])
422
            else:
423
                self.client.create_directory(self.path)
424
        except ClientError as err:
425
            raiseCLIError(err)
426

    
427

    
428
@command(pithos_cmds)
429
class store_copy(_store_container_command):
430
    """Copy an object from container to (another) container
431
    Use options:
432
    1. <container1>:<path1> <container2>[:path2] : from container1 to 2, if
433
        path2 is not given, target path will be container2:path1
434
    2. <container>:<path1> <container>:<path2> : make a copy in the same
435
        container
436
    3. Use --container= instead of <container1>, but only for the first
437
        parameter
438
    """
439

    
440
    arguments = dict(
441
        source_version=ValueArgument(
442
            'copy specific version',
443
            '--source-version'),
444
        public=ValueArgument('make object publicly accessible', '--public'),
445
        content_type=ValueArgument(
446
            'change object\'s content type',
447
            '--content-type'),
448
        recursive=FlagArgument(
449
            'mass copy with delimiter /',
450
            ('-r', '--recursive'))
451
    )
452

    
453
    def __init__(self, arguments={}):
454
        super(self.__class__, self).__init__(arguments)
455
        self['delimiter']=DelimiterArgument(
456
            self,
457
            parsed_name='--delimiter',
458
            help=u'copy objects prefixed as src_object + delimiter')
459

    
460
    def main(self, source_container___path, destination_container____path__):
461
        super(self.__class__,
462
            self).main(source_container___path, path_is_optional=False)
463
        try:
464
            dst = destination_container____path__.split(':')
465
            dst_cont = dst[0]
466
            dst_path = dst[1] if len(dst) > 1 else False
467
            self.client.copy_object(src_container=self.container,
468
                src_object=self.path,
469
                dst_container=dst_cont,
470
                dst_object=dst_path,
471
                source_version=self['source_version'],
472
                public=self['public'],
473
                content_type=self['content_type'],
474
                delimiter=self['delimiter'])
475
        except ClientError as err:
476
            raiseCLIError(err)
477

    
478

    
479
@command(pithos_cmds)
480
class store_move(_store_container_command):
481
    """Copy an object
482
    Use options:
483
    1. <container1>:<path1> <container2>[:path2] : from container1 to 2, if
484
        path2 is not given, target path will be container2:path1
485
    2. <container>:<path1> <container>:<path2> : rename
486
    3. Use --container= instead of <container1>, but only for the first
487
        parameter
488
    """
489

    
490
    arguments = dict(
491
        source_version=ValueArgument('specify version', '--source-version'),
492
        public=FlagArgument('make object publicly accessible', '--public'),
493
        content_type=ValueArgument('modify content type', '--content-type'),
494
        recursive= FlagArgument('up to delimiter /', ('-r', '--recursive'))
495
    )
496

    
497
    def __init__(self, arguments={}):
498
        super(self.__class__, self).__init__(arguments)
499
        self['delimiter']=DelimiterArgument(
500
            self,
501
            parsed_name='--delimiter',
502
            help=u'move objects prefixed as src_object + delimiter')
503

    
504
    def main(self, source_container___path, destination_container____path__):
505
        super(self.__class__,
506
            self).main(source_container___path, path_is_optional=False)
507
        try:
508
            dst = destination_container____path__.split(':')
509
            dst_cont = dst[0]
510
            dst_path = dst[1] if len(dst) > 1 else False
511
            self.client.move_object(src_container=self.container,
512
                src_object=self.path,
513
                dst_container=dst_cont,
514
                dst_object=dst_path,
515
                source_version=self['source_version'],
516
                public=self['public'],
517
                content_type=self['content_type'],
518
                delimiter=self['delimiter'])
519
        except ClientError as err:
520
            raiseCLIError(err)
521

    
522

    
523
@command(pithos_cmds)
524
class store_append(_store_container_command):
525
    """Append local file to (existing) remote object"""
526

    
527
    arguments = dict(
528
        progress_bar=ProgressBarArgument(
529
            'do not show progress bar',
530
            '--no-progress-bar',
531
            default=False)
532
    )
533

    
534
    def main(self, local_path, container___path):
535
        super(self.__class__,
536
            self).main(container___path, path_is_optional=False)
537
        try:
538
            f = open(local_path, 'rb')
539
            progress_bar = self.arguments['progress_bar']
540
            try:
541
                upload_cb = progress_bar.get_generator('Appending blocks')
542
            except Exception:
543
                upload_cb = None
544
            self.client.append_object(object=self.path,
545
                source_file=f,
546
                upload_cb=upload_cb)
547
        except ClientError as err:
548
            progress_bar.finish()
549
            raiseCLIError(err)
550
        finally:
551
            progress_bar.finish()
552

    
553

    
554
@command(pithos_cmds)
555
class store_truncate(_store_container_command):
556
    """Truncate remote file up to a size"""
557

    
558
    def main(self, container___path, size=0):
559
        super(self.__class__,
560
            self).main(container___path, path_is_optional=False)
561
        try:
562
            self.client.truncate_object(self.path, size)
563
        except ClientError as err:
564
            raiseCLIError(err)
565

    
566

    
567
@command(pithos_cmds)
568
class store_overwrite(_store_container_command):
569
    """Overwrite part (from start to end) of a remote file"""
570

    
571
    arguments = dict(
572
        progress_bar=ProgressBarArgument(
573
            'do not show progress bar',
574
            '--no-progress-bar', 
575
            default=False)
576
    )
577

    
578
    def main(self, local_path, container___path, start, end):
579
        super(self.__class__,
580
            self).main(container___path, path_is_optional=False)
581
        try:
582
            f = open(local_path, 'rb')
583
            progress_bar = self.arguments['progress_bar']
584
            try:
585
                upload_cb = progress_bar.get_generator('Overwritting blocks')
586
            except Exception:
587
                upload_cb = None
588
            self.client.overwrite_object(object=self.path,
589
                start=start,
590
                end=end,
591
                source_file=f,
592
                upload_cb=upload_cb)
593
        except ClientError as err:
594
            progress_bar.finish()
595
            raiseCLIError(err)
596
        finally:
597
            progress_bar.finish()
598

    
599

    
600
@command(pithos_cmds)
601
class store_manifest(_store_container_command):
602
    """Create a remote file with uploaded parts by manifestation"""
603

    
604
    arguments = dict(
605
        etag=ValueArgument('check written data', '--etag'),
606
        content_encoding=ValueArgument(
607
            'set MIME content type',
608
            '--content-encoding'),
609
        content_disposition=ValueArgument(
610
            'the presentation style of the object',
611
            '--content-disposition'),
612
        content_type=ValueArgument('specify content type', '--content-type'),
613
        sharing=SharingArgument(
614
            'define object sharing policy \n' +\
615
            '    ( "read=user1,grp1,user2,... write=user1,grp2,..." )',
616
            '--sharing'),
617
        public=FlagArgument('make object publicly accessible', '--public')
618
    )
619

    
620
    def main(self, container___path):
621
        super(self.__class__,
622
            self).main(container___path, path_is_optional=False)
623
        try:
624
            self.client.create_object_by_manifestation(
625
                self.path,
626
                content_encoding=self['content_encoding'],
627
                content_disposition=self['content_disposition'],
628
                content_type=self['content_type'],
629
                sharing=self['sharing'],
630
                public=self['public'])
631
        except ClientError as err:
632
            raiseCLIError(err)
633

    
634

    
635
@command(pithos_cmds)
636
class store_upload(_store_container_command):
637
    """Upload a file"""
638

    
639
    arguments = dict(
640
        use_hashes=FlagArgument(
641
            'provide hashmap file instead of data',
642
            '--use-hashes'),
643
        etag=ValueArgument('check written data', '--etag'),
644
        unchunked=FlagArgument('avoid chunked transfer mode', '--unchunked'),
645
        content_encoding=ValueArgument(
646
            'set MIME content type',
647
            '--content-encoding'),
648
        content_disposition=ValueArgument(
649
            'specify objects presentation style',
650
            '--content-disposition'),
651
        content_type=ValueArgument('specify content type', '--content-type'),
652
        sharing=SharingArgument(
653
            help='define sharing object policy \n' +\
654
            '( "read=user1,grp1,user2,... write=user1,grp2,... )',
655
            parsed_name='--sharing'),
656
        public=FlagArgument('make object publicly accessible', '--public'),
657
        poolsize=IntArgument('set pool size', '--with-pool-size'),
658
        progress_bar=ProgressBarArgument(
659
            'do not show progress bar',
660
            '--no-progress-bar',
661
            default=False)
662
    )
663

    
664
    def main(self, local_path, container____path__):
665
        super(self.__class__, self).main(container____path__)
666
        remote_path = self.path if self.path else local_path
667
        poolsize = self['poolsize']
668
        if poolsize is not None:
669
            self.client.POOL_SIZE = int(poolsize)
670
        params = dict(content_encoding=self['content_encoding'],
671
            content_type=self['content_type'],
672
            content_disposition=self['content_disposition'],
673
            sharing=self['sharing'],
674
            public=self['public'])
675
        try:
676
            progress_bar = self.arguments['progress_bar']
677
            hash_bar = progress_bar.clone()
678
            with open(local_path, 'rb') as f:
679
                if self['unchunked']:
680
                    self.client.upload_object_unchunked(
681
                        remote_path,
682
                        f,
683
                        etag=self['etag'],
684
                        withHashFile=self['use_hashes'],
685
                        **params)
686
                else:
687
                    hash_cb = hash_bar.get_generator(
688
                        'Calculating block hashes')
689
                    upload_cb = progress_bar.get_generator('Uploading')
690
                    self.client.upload_object(
691
                        remote_path,
692
                        f,
693
                        hash_cb=hash_cb,
694
                        upload_cb=upload_cb,
695
                        **params)
696
                    progress_bar.finish()
697
                    hash_bar.finish()
698
        except ClientError as err:
699
            progress_bar.finish()
700
            hash_bar.finish()
701
            raiseCLIError(err, '"%s" not accessible' % container____path__)
702
        except IOError as err:
703
            progress_bar.finish()
704
            hash_bar.finish()
705
            raiseCLIError(err, 'Failed to read form file %s' % local_path, 2)
706
        print 'Upload completed'
707

    
708

    
709
@command(pithos_cmds)
710
class store_cat(_store_container_command):
711
    """Print a file to console"""
712

    
713
    arguments = dict(
714
        range=RangeArgument('show range of data', '--range'),
715
        if_match=ValueArgument('show output if ETags match', '--if-match'),
716
        if_none_match=ValueArgument(
717
            'show output if ETags match',
718
            '--if-none-match'),
719
        if_modified_since=DateArgument(
720
            'show output modified since then',
721
            '--if-modified-since'),
722
        if_unmodified_since=DateArgument(
723
            'show output unmodified since then',
724
            '--if-unmodified-since'),
725
        object_version=ValueArgument(
726
            'get the specific version',
727
            '--object-version')
728
    )
729

    
730

    
731
    def main(self, container___path):
732
        super(self.__class__,
733
            self).main(container___path, path_is_optional=False)
734
        try:
735
            self.client.download_object(self.path, stdout,
736
            range=self['range'],
737
            version=self['object_version'],
738
            if_match=self['if_match'],
739
            if_none_match=self['if_none_match'],
740
            if_modified_since=self['if_modified_since'],
741
            if_unmodified_since=self['if_unmodified_since'])
742
        except ClientError as err:
743
            raiseCLIError(err)
744

    
745

    
746
@command(pithos_cmds)
747
class store_download(_store_container_command):
748
    """Download a file"""
749

    
750
    arguments = dict(
751
        resume=FlagArgument('Resume instead of overwrite', '--resume'),
752
        range=RangeArgument('show range of data', '--range'),
753
        if_match=ValueArgument('show output if ETags match', '--if-match'),
754
        if_none_match=ValueArgument(
755
            'show output if ETags match',
756
            '--if-none-match'),
757
        if_modified_since=DateArgument(
758
            'show output modified since then',
759
            '--if-modified-since'),
760
        if_unmodified_since=DateArgument(
761
            'show output unmodified since then',
762
            '--if-unmodified-since'),
763
        object_version=ValueArgument(
764
            'get the specific version',
765
            '--object-version'),
766
        poolsize=IntArgument('set pool size', '--with-pool-size'),
767
        progress_bar=ProgressBarArgument(
768
            'do not show progress bar',
769
            '--no-progress-bar',
770
            default=False)
771
    )
772

    
773
    def main(self, container___path, local_path):
774
        super(self.__class__,
775
            self).main(container___path, path_is_optional=False)
776

    
777
        # setup output stream
778
        if local_path is None:
779
            out = stdout
780
        else:
781
            try:
782
                if self['resume']:
783
                    out = open(local_path, 'rwb+')
784
                else:
785
                    out = open(local_path, 'wb+')
786
            except IOError as err:
787
                raiseCLIError(err, 'Cannot write to file %s' % local_path, 1)
788
        poolsize = self['poolsize']
789
        if poolsize is not None:
790
            self.client.POOL_SIZE = int(poolsize)
791

    
792
        try:
793
            progress_bar = self.arguments['progress_bar']
794
            download_cb = progress_bar.get_generator('Downloading')
795
            self.client.download_object(self.path, out,
796
                download_cb=download_cb,
797
                range=self['range'],
798
                version=self['object_version'],
799
                if_match=self['if_match'],
800
                resume=self['resume'],
801
                if_none_match=self['if_none_match'],
802
                if_modified_since=self['if_modified_since'],
803
                if_unmodified_since=self['if_unmodified_since'])
804
            progress_bar.finish()
805
        except ClientError as err:
806
            progress_bar.finish()
807
            raiseCLIError(err)
808
        except KeyboardInterrupt:
809
            from threading import enumerate as activethreads
810
            stdout.write('\nFinishing active threads ')
811
            for thread in activethreads():
812
                stdout.flush()
813
                try:
814
                    thread.join()
815
                    stdout.write('.')
816
                except RuntimeError:
817
                    continue
818
            progress_bar.finish()
819
            print('\ndownload canceled by user')
820
            if local_path is not None:
821
                print('to resume, re-run with --resume')
822
        except Exception as e:
823
            progress_bar.finish()
824
            raiseCLIError(e)
825
        print
826

    
827

    
828
@command(pithos_cmds)
829
class store_hashmap(_store_container_command):
830
    """Get the hashmap of an object"""
831

    
832
    arguments = dict(
833
        if_match=ValueArgument('show output if ETags match', '--if-match'),
834
        if_none_match=ValueArgument(
835
            'show output if ETags match',
836
            '--if-none-match'),
837
        if_modified_since=DateArgument(
838
            'show output modified since then',
839
            '--if-modified-since'),
840
        if_unmodified_since=DateArgument(
841
            'show output unmodified since then',
842
            '--if-unmodified-since'),
843
        object_version=ValueArgument(
844
            'get the specific version',
845
            '--object-version')
846
    )
847

    
848
    def main(self, container___path):
849
        super(self.__class__,
850
            self).main(container___path, path_is_optional=False)
851
        try:
852
            data = self.client.get_object_hashmap(
853
                self.path,
854
                version=self['object_version'],
855
                if_match=self['if_match'],
856
                if_none_match=self['if_none_match'],
857
                if_modified_since=self['if_modified_since'],
858
                if_unmodified_since=self['if_unmodified_since'])
859
        except ClientError as err:
860
            raiseCLIError(err)
861
        print_dict(data)
862

    
863

    
864
@command(pithos_cmds)
865
class store_delete(_store_container_command):
866
    """Delete a container [or an object]"""
867

    
868
    arguments = dict(
869
        until=DateArgument( 'remove history until that date', '--until'),
870
        recursive=FlagArgument(
871
            'empty dir or container and delete (if dir)',
872
            ('-r', '--recursive'))
873
    )
874

    
875
    def __init__(self, arguments={}):
876
        super(self.__class__, self).__init__(arguments)
877
        self['delimiter']=DelimiterArgument(
878
            self,
879
            parsed_name='--delimiter',
880
            help='delete objects prefixed with <object><delimiter>')
881

    
882
    def main(self, container____path__):
883
        super(self.__class__, self).main(container____path__)
884
        try:
885
            if self.path is None:
886
                self.client.del_container(
887
                    until=self['until'],
888
                    delimiter=self['delimiter'])
889
            else:
890
                # self.client.delete_object(self.path)
891
                self.client.del_object(
892
                    self.path,
893
                    until=self['until'],
894
                    delimiter=self['delimiter'])
895
        except ClientError as err:
896
            raiseCLIError(err)
897

    
898

    
899
@command(pithos_cmds)
900
class store_purge(_store_container_command):
901
    """Purge a container
902
    To completely erase a container:
903
    /store delete -r <container>
904
    /store purge <container
905
    """
906

    
907
    def main(self, container):
908
        super(self.__class__, self).main(container)
909
        try:
910
            self.client.purge_container()
911
        except ClientError as err:
912
            raiseCLIError(err)
913

    
914

    
915
@command(pithos_cmds)
916
class store_publish(_store_container_command):
917
    """Publish the object and print the public url"""
918

    
919
    def main(self, container___path):
920
        super(self.__class__,
921
            self).main(container___path, path_is_optional=False)
922
        try:
923
            url = self.client.publish_object(self.path)
924
        except ClientError as err:
925
            raiseCLIError(err)
926
        print(url)
927

    
928

    
929
@command(pithos_cmds)
930
class store_unpublish(_store_container_command):
931
    """Unpublish an object"""
932

    
933
    def main(self, container___path):
934
        super(self.__class__,
935
            self).main(container___path, path_is_optional=False)
936
        try:
937
            self.client.unpublish_object(self.path)
938
        except ClientError as err:
939
            raiseCLIError(err)
940

    
941

    
942
@command(pithos_cmds)
943
class store_permissions(_store_container_command):
944
    """Get object read / write permissions """
945

    
946
    def main(self, container___path):
947
        super(self.__class__,
948
            self).main(container___path, path_is_optional=False)
949
        try:
950
            reply = self.client.get_object_sharing(self.path)
951
            print_dict(reply)
952
        except ClientError as err:
953
            raiseCLIError(err)
954

    
955

    
956
@command(pithos_cmds)
957
class store_setpermissions(_store_container_command):
958
    """Set sharing permissions """
959

    
960
    def format_permition_dict(self, permissions):
961
        read = False
962
        write = False
963
        for perms in permissions:
964
            splstr = perms.split('=')
965
            if 'read' == splstr[0]:
966
                read = [user_or_group.strip() \
967
                for user_or_group in splstr[1].split(',')]
968
            elif 'write' == splstr[0]:
969
                write = [user_or_group.strip() \
970
                for user_or_group in splstr[1].split(',')]
971
            else:
972
                read = False
973
                write = False
974
        if not read and not write:
975
            raiseCLIError(None,
976
            'Usage:\tread=<groups,users> write=<groups,users>')
977
        return (read, write)
978

    
979
    def main(self, container___path, *permissions):
980
        super(self.__class__,
981
            self).main(container___path, path_is_optional=False)
982
        (read, write) = self.format_permition_dict(permissions)
983
        try:
984
            self.client.set_object_sharing(self.path,
985
                read_permition=read, write_permition=write)
986
        except ClientError as err:
987
            raiseCLIError(err)
988

    
989

    
990
@command(pithos_cmds)
991
class store_delpermissions(_store_container_command):
992
    """Delete all sharing permissions"""
993

    
994
    def main(self, container___path):
995
        super(self.__class__,
996
            self).main(container___path, path_is_optional=False)
997
        try:
998
            self.client.del_object_sharing(self.path)
999
        except ClientError as err:
1000
            raiseCLIError(err)
1001

    
1002

    
1003
@command(pithos_cmds)
1004
class store_info(_store_container_command):
1005
    """Get information for account [, container [or object]]"""
1006

    
1007
    arguments = dict(
1008
        object_version=ValueArgument(
1009
            'show specific version \ (applies only for objects)',
1010
            '--object-version')
1011
    )
1012

    
1013
    def main(self, container____path__=None):
1014
        super(self.__class__, self).main(container____path__)
1015
        try:
1016
            if self.container is None:
1017
                reply = self.client.get_account_info()
1018
            elif self.path is None:
1019
                reply = self.client.get_container_info(self.container)
1020
            else:
1021
                reply = self.client.get_object_info(
1022
                    self.path,
1023
                    version=self['object_version'])
1024
        except ClientError as err:
1025
            raiseCLIError(err)
1026
        print_dict(reply)
1027

    
1028

    
1029
@command(pithos_cmds)
1030
class store_meta(_store_container_command):
1031
    """Get custom meta-content for account [, container [or object]]"""
1032

    
1033
    arguments = dict(
1034
        detail=FlagArgument('show detailed output', '-l'),
1035
        until=DateArgument('show metadata until then', '--until'),
1036
        object_version=ValueArgument(
1037
            'show specific version \ (applies only for objects)',
1038
            '--object-version')
1039
    )
1040

    
1041
    def main(self, container____path__=None):
1042
        super(self.__class__, self).main(container____path__)
1043

    
1044
        detail = self['detail']
1045
        try:
1046
            until = self['until']
1047
            if self.container is None:
1048
                print(bold(self.client.account))
1049
                if detail:
1050
                    reply = self.client.get_account_info(until=until)
1051
                else:
1052
                    reply = self.client.get_account_meta(until=until)
1053
                    reply = pretty_keys(reply, '-')
1054
            elif self.path is None:
1055
                print(bold('%s: %s' % (self.client.account, self.container)))
1056
                if detail:
1057
                    reply = self.client.get_container_info(until=until)
1058
                else:
1059
                    cmeta = self.client.get_container_meta(until=until)
1060
                    ometa = self.client.get_container_object_meta(until=until)
1061
                    reply = {'container-meta': pretty_keys(cmeta, '-'),
1062
                        'object-meta': pretty_keys(ometa, '-')}
1063
            else:
1064
                print(bold('%s: %s:%s'\
1065
                    % (self.client.account, self.container, self.path)))
1066
                version = self['object_version']
1067
                if detail:
1068
                    reply = self.client.get_object_info(self.path,
1069
                        version=version)
1070
                else:
1071
                    reply = self.client.get_object_meta(self.path,
1072
                        version=version)
1073
                    reply = pretty_keys(pretty_keys(reply, '-'))
1074
        except ClientError as err:
1075
            raiseCLIError(err)
1076
        print_dict(reply)
1077

    
1078

    
1079
@command(pithos_cmds)
1080
class store_setmeta(_store_container_command):
1081
    """Set a new metadatum for account [, container [or object]]"""
1082

    
1083
    def main(self, metakey___metaval, container____path__=None):
1084
        super(self.__class__, self).main(container____path__)
1085
        try:
1086
            metakey, metavalue = metakey___metaval.split(':')
1087
        except ValueError as err:
1088
            raiseCLIError(err, 'Usage:  metakey:metavalue', importance=1)
1089
        try:
1090
            if self.container is None:
1091
                self.client.set_account_meta({metakey: metavalue})
1092
            elif self.path is None:
1093
                self.client.set_container_meta({metakey: metavalue})
1094
            else:
1095
                self.client.set_object_meta(self.path, {metakey: metavalue})
1096
        except ClientError as err:
1097
            raiseCLIError(err)
1098

    
1099

    
1100
@command(pithos_cmds)
1101
class store_delmeta(_store_container_command):
1102
    """Delete an existing metadatum of account [, container [or object]]"""
1103

    
1104
    def main(self, metakey, container____path__=None):
1105
        super(self.__class__, self).main(container____path__)
1106
        try:
1107
            if self.container is None:
1108
                self.client.del_account_meta(metakey)
1109
            elif self.path is None:
1110
                self.client.del_container_meta(metakey)
1111
            else:
1112
                self.client.del_object_meta(self.path, metakey)
1113
        except ClientError as err:
1114
            raiseCLIError(err)
1115

    
1116

    
1117
@command(pithos_cmds)
1118
class store_quota(_store_account_command):
1119
    """Get  quota for account [or container]"""
1120

    
1121
    def main(self, container=None):
1122
        super(self.__class__, self).main()
1123
        try:
1124
            if container is None:
1125
                reply = self.client.get_account_quota()
1126
            else:
1127
                reply = self.client.get_container_quota(container)
1128
        except ClientError as err:
1129
            raiseCLIError(err)
1130
        print_dict(reply)
1131

    
1132

    
1133
@command(pithos_cmds)
1134
class store_setquota(_store_account_command):
1135
    """Set new quota (in KB) for account [or container]"""
1136

    
1137
    def main(self, quota, container=None):
1138
        super(self.__class__, self).main()
1139
        try:
1140
            if container is None:
1141
                self.client.set_account_quota(quota)
1142
            else:
1143
                self.client.container = container
1144
                self.client.set_container_quota(quota)
1145
        except ClientError as err:
1146
            raiseCLIError(err)
1147

    
1148

    
1149
@command(pithos_cmds)
1150
class store_versioning(_store_account_command):
1151
    """Get  versioning for account [or container ]"""
1152

    
1153
    def main(self, container=None):
1154
        super(self.__class__, self).main()
1155
        try:
1156
            if container is None:
1157
                reply = self.client.get_account_versioning()
1158
            else:
1159
                reply = self.client.get_container_versioning(container)
1160
        except ClientError as err:
1161
            raiseCLIError(err)
1162
        print_dict(reply)
1163

    
1164

    
1165
@command(pithos_cmds)
1166
class store_setversioning(_store_account_command):
1167
    """Set new versioning (auto, none) for account [or container]"""
1168

    
1169
    def main(self, versioning, container=None):
1170
        super(self.__class__, self).main()
1171
        try:
1172
            if container is None:
1173
                self.client.set_account_versioning(versioning)
1174
            else:
1175
                self.client.container = container
1176
                self.client.set_container_versioning(versioning)
1177
        except ClientError as err:
1178
            raiseCLIError(err)
1179

    
1180

    
1181
@command(pithos_cmds)
1182
class store_group(_store_account_command):
1183
    """Get user groups details for account"""
1184

    
1185
    def main(self):
1186
        super(self.__class__, self).main()
1187
        try:
1188
            reply = self.client.get_account_group()
1189
        except ClientError as err:
1190
            raiseCLIError(err)
1191
        print_dict(reply)
1192

    
1193

    
1194
@command(pithos_cmds)
1195
class store_setgroup(_store_account_command):
1196
    """Create/update a new user group on account"""
1197

    
1198
    def main(self, groupname, *users):
1199
        super(self.__class__, self).main()
1200
        try:
1201
            self.client.set_account_group(groupname, users)
1202
        except ClientError as err:
1203
            raiseCLIError(err)
1204

    
1205

    
1206
@command(pithos_cmds)
1207
class store_delgroup(_store_account_command):
1208
    """Delete a user group on an account"""
1209

    
1210
    def main(self, groupname):
1211
        super(self.__class__, self).main()
1212
        try:
1213
            self.client.del_account_group(groupname)
1214
        except ClientError as err:
1215
            raiseCLIError(err)
1216

    
1217

    
1218
@command(pithos_cmds)
1219
class store_sharers(_store_account_command):
1220
    """List the accounts that share objects with default account"""
1221

    
1222
    arguments = dict(
1223
        detail=FlagArgument('show detailed output', '-l'),
1224
        marker=ValueArgument('show output greater then marker', '--marker')
1225
    )
1226

    
1227
    def main(self):
1228
        super(self.__class__, self).main()
1229
        try:
1230
            marker = self['marker']
1231
            accounts = self.client.get_sharing_accounts(marker=marker)
1232
        except ClientError as err:
1233
            raiseCLIError(err)
1234

    
1235
        for acc in accounts:
1236
            stdout.write(bold(acc['name']) + ' ')
1237
            if self['detail']:
1238
                print_dict(acc, exclude='name', ident=4)
1239
        if not self['detail']:
1240
            print
1241

    
1242

    
1243
@command(pithos_cmds)
1244
class store_versions(_store_container_command):
1245
    """Get the version list of an object"""
1246

    
1247
    def main(self, container___path):
1248
        super(store_versions, self).main(container___path)
1249
        try:
1250
            versions = self.client.get_object_versionlist(self.path)
1251
        except ClientError as err:
1252
            raiseCLIError(err)
1253

    
1254
        print('%s:%s versions' % (self.container, self.path))
1255
        for vitem in versions:
1256
            t = localtime(float(vitem[1]))
1257
            vid = bold(unicode(vitem[0]))
1258
            print('\t%s \t(%s)' % (vid, strftime('%d-%m-%Y %H:%M:%S', t)))
1259

    
1260

    
1261
@command(pithos_cmds)
1262
class store_touch(_store_container_command):
1263
    """Create an empty file of type application/octet-stream
1264
    If object exists, this command will make reset it to 0 length
1265
    """
1266

    
1267
    def main(self, container___path, content_type='application/octet-stream'):
1268
        super(store_touch, self).main(container___path)
1269
        try:
1270
            self.client.create_object(self.path, content_type)
1271
        except ClientError as err:
1272
            raiseCLIError(err)