Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 8fa6ef6a

History | View | Annotate | Download (53 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, page_hold
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
from os import path
48

    
49
kloger = getLogger('kamaki')
50

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

    
54

    
55
about_directories = [
56
    'Kamaki hanldes directories the same way as OOS Storage and Pithos+:',
57
    'A directory is an object with type "application/directory"',
58
    'An object with path dir/name can exist even if dir does not exist or',
59
    'even if dir is a non directory object. Users can modify dir without',
60
    'affecting the dir/name object in any way.']
61

    
62

    
63
# Argument functionality
64

    
65
def raise_connection_errors(e):
66
    if e.status in range(200) + [403]:
67
        raiseCLIError(e, details=[
68
            'Please check the service url and the authentication information',
69
            ' ',
70
            '  to get the service url: /config get store.url',
71
            '  to set the service url: /config set store.url <url>',
72
            ' ',
73
            '  to get user the account: /config get store.account',
74
            '           or              /config get account',
75
            '  to set the user account: /config set store.account <account>',
76
            ' ',
77
            '  to get authentication token: /config get token',
78
            '  to set authentication token: /config set token <token>'
79
            ])
80

    
81

    
82
class DelimiterArgument(ValueArgument):
83
    """
84
    :value type: string
85
    :value returns: given string or /
86
    """
87

    
88
    def __init__(self, caller_obj, help='', parsed_name=None, default=None):
89
        super(DelimiterArgument, self).__init__(help, parsed_name, default)
90
        self.caller_obj = caller_obj
91

    
92
    @property
93
    def value(self):
94
        if self.caller_obj['recursive']:
95
            return '/'
96
        return getattr(self, '_value', self.default)
97

    
98
    @value.setter
99
    def value(self, newvalue):
100
        self._value = newvalue
101

    
102

    
103
class SharingArgument(ValueArgument):
104
    """Set sharing (read and/or write) groups
105
    .
106
    :value type: "read=term1,term2,... write=term1,term2,..."
107
    .
108
    :value returns: {'read':['term1', 'term2', ...],
109
    .   'write':['term1', 'term2', ...]}
110
    """
111

    
112
    @property
113
    def value(self):
114
        return getattr(self, '_value', self.default)
115

    
116
    @value.setter
117
    def value(self, newvalue):
118
        perms = {}
119
        try:
120
            permlist = newvalue.split(' ')
121
        except AttributeError:
122
            return
123
        for p in permlist:
124
            try:
125
                (key, val) = p.split('=')
126
            except ValueError as err:
127
                raiseCLIError(err, 'Error in --sharing',
128
                    details='Incorrect format',
129
                    importance=1)
130
            if key.lower() not in ('read', 'write'):
131
                raiseCLIError(err, 'Error in --sharing',
132
                    details='Invalid permission key %s' % key,
133
                    importance=1)
134
            val_list = val.split(',')
135
            if not key in perms:
136
                perms[key] = []
137
            for item in val_list:
138
                if item not in perms[key]:
139
                    perms[key].append(item)
140
        self._value = perms
141

    
142

    
143
class RangeArgument(ValueArgument):
144
    """
145
    :value type: string of the form <start>-<end> where <start> and <end> are
146
        integers
147
    :value returns: the input string, after type checking <start> and <end>
148
    """
149

    
150
    @property
151
    def value(self):
152
        return getattr(self, '_value', self.default)
153

    
154
    @value.setter
155
    def value(self, newvalue):
156
        if newvalue is None:
157
            self._value = self.default
158
            return
159
        (start, end) = newvalue.split('-')
160
        (start, end) = (int(start), int(end))
161
        self._value = '%s-%s' % (start, end)
162

    
163
# Command specs
164

    
165

    
166
class _pithos_init(_command_init):
167
    """Initialize a pithos+ kamaki client"""
168

    
169
    def main(self):
170
        self.token = self.config.get('store', 'token')\
171
            or self.config.get('global', 'token')
172
        self.base_url = self.config.get('store', 'url')\
173
            or self.config.get('global', 'url')
174
        self.account = self.config.get('store', 'account')\
175
            or self.config.get('global', 'account')
176
        self.container = self.config.get('store', 'container')\
177
            or self.config.get('global', 'container')
178
        self.client = PithosClient(base_url=self.base_url,
179
            token=self.token,
180
            account=self.account,
181
            container=self.container)
182

    
183

    
184
class _store_account_command(_pithos_init):
185
    """Base class for account level storage commands"""
186

    
187
    def __init__(self, arguments={}):
188
        super(_store_account_command, self).__init__(arguments)
189
        self['account'] = ValueArgument(
190
            'Set user account (not permanent)',
191
            '--account')
192

    
193
    def main(self):
194
        super(_store_account_command, self).main()
195
        if self['account']:
196
            self.client.account = self['account']
197

    
198

    
199
class _store_container_command(_store_account_command):
200
    """Base class for container level storage commands"""
201

    
202
    generic_err_details = ['To specify a container:',
203
    '  1. Set store.container variable (permanent)',
204
    '     /config set store.container <container>',
205
    '  2. --container=<container> (temporary, overrides 1)',
206
    '  3. Use the container:path format (temporary, overrides all)']
207
    container = None
208
    path = None
209

    
210
    def __init__(self, arguments={}):
211
        super(_store_container_command, self).__init__(arguments)
212
        self['container'] = ValueArgument(
213
            'Set container to work with (temporary)',
214
            '--container')
215

    
216
    def extract_container_and_path(self,
217
        container_with_path,
218
        path_is_optional=True):
219
        try:
220
            assert isinstance(container_with_path, str)
221
        except AssertionError as err:
222
            raiseCLIError(err)
223

    
224
        cont, sep, path = container_with_path.partition(':')
225

    
226
        if sep:
227
            if not cont:
228
                raiseCLIError(CLISyntaxError('Container is missing\n',
229
                    details=self.generic_err_details))
230
            alt_cont = self['container']
231
            if alt_cont and cont != alt_cont:
232
                raiseCLIError(CLISyntaxError(
233
                    'Conflict: 2 containers (%s, %s)' % (cont, alt_cont),
234
                    details=self.generic_err_details)
235
                )
236
            self.container = cont
237
            if not path:
238
                raiseCLIError(CLISyntaxError(
239
                    'Path is missing for object in container %s' % cont,
240
                    details=self.generic_err_details)
241
                )
242
            self.path = path
243
        else:
244
            alt_cont = self['container'] or self.client.container
245
            if alt_cont:
246
                self.container = alt_cont
247
                self.path = cont
248
            elif path_is_optional:
249
                self.container = cont
250
                self.path = None
251
            else:
252
                self.container = cont
253
                raiseCLIError(CLISyntaxError(
254
                    'Both container and path are required',
255
                    details=self.generic_err_details)
256
                )
257

    
258
    def main(self, container_with_path=None, path_is_optional=True):
259
        super(_store_container_command, self).main()
260
        if container_with_path is not None:
261
            self.extract_container_and_path(
262
                container_with_path,
263
                path_is_optional)
264
            self.client.container = self.container
265
        elif self['container']:
266
            self.client.container = self['container']
267
        self.container = self.client.container
268

    
269

    
270
@command(pithos_cmds)
271
class store_list(_store_container_command):
272
    """List containers, object trees or objects in a directory
273
    Use with:
274
    1 no parameters : containers in set account
275
    2. one parameter (container) or --container : contents of container
276
    3. <container>:<prefix> or --container=<container> <prefix>: objects in
277
    .   container starting with prefix
278
    """
279

    
280
    arguments = dict(
281
        detail=FlagArgument('show detailed output', '-l'),
282
        limit=IntArgument('limit the number of listed items', '-n'),
283
        marker=ValueArgument('show output greater that marker', '--marker'),
284
        prefix=ValueArgument('show output starting with prefix', '--prefix'),
285
        delimiter=ValueArgument('show output up to delimiter', '--delimiter'),
286
        path=ValueArgument(
287
            'show output starting with prefix up to /',
288
            '--path'),
289
        meta=ValueArgument(
290
            'show output with specified meta keys',
291
            '--meta',
292
            default=[]),
293
        if_modified_since=ValueArgument(
294
            'show output modified since then',
295
            '--if-modified-since'),
296
        if_unmodified_since=ValueArgument(
297
            'show output not modified since then',
298
            '--if-unmodified-since'),
299
        until=DateArgument('show metadata until then', '--until'),
300
        format=ValueArgument(
301
            'format to parse until data (default: d/m/Y H:M:S )',
302
            '--format'),
303
        shared=FlagArgument('show only shared', '--shared'),
304
        public=FlagArgument('show only public', '--public'),
305
        more=FlagArgument(
306
            'output results in pages (-n to set items per page, default 10)',
307
            '--more')
308
    )
309

    
310
    def print_objects(self, object_list):
311
        limit = int(self['limit']) if self['limit'] > 0 else len(object_list)
312
        for index, obj in enumerate(object_list):
313
            if 'content_type' not in obj:
314
                continue
315
            pretty_obj = obj.copy()
316
            index += 1
317
            empty_space = ' ' * (len(str(len(object_list))) - len(str(index)))
318
            if obj['content_type'] == 'application/directory':
319
                isDir = True
320
                size = 'D'
321
            else:
322
                isDir = False
323
                size = format_size(obj['bytes'])
324
                pretty_obj['bytes'] = '%s (%s)' % (obj['bytes'], size)
325
            oname = bold(obj['name'])
326
            if self['detail']:
327
                print('%s%s. %s' % (empty_space, index, oname))
328
                print_dict(pretty_keys(pretty_obj), exclude=('name'))
329
                print
330
            else:
331
                oname = '%s%s. %6s %s' % (empty_space, index, size, oname)
332
                oname += '/' if isDir else ''
333
                print(oname)
334
            if self['more']:
335
                page_hold(index, limit, len(object_list))
336

    
337
    def print_containers(self, container_list):
338
        limit = int(self['limit']) if self['limit'] > 0\
339
            else len(container_list)
340
        for index, container in enumerate(container_list):
341
            if 'bytes' in container:
342
                size = format_size(container['bytes'])
343
            cname = '%s. %s' % (index + 1, bold(container['name']))
344
            if self['detail']:
345
                print(cname)
346
                pretty_c = container.copy()
347
                if 'bytes' in container:
348
                    pretty_c['bytes'] = '%s (%s)' % (container['bytes'], size)
349
                print_dict(pretty_keys(pretty_c), exclude=('name'))
350
                print
351
            else:
352
                if 'count' in container and 'bytes' in container:
353
                    print('%s (%s, %s objects)'\
354
                    % (cname, size, container['count']))
355
                else:
356
                    print(cname)
357
            if self['more']:
358
                page_hold(index + 1, limit, len(container_list))
359

    
360
    def main(self, container____path__=None):
361
        super(self.__class__, self).main(container____path__)
362
        try:
363
            if self.container is None:
364
                r = self.client.account_get(
365
                    limit=False if self['more'] else self['limit'],
366
                    marker=self['marker'],
367
                    if_modified_since=self['if_modified_since'],
368
                    if_unmodified_since=self['if_unmodified_since'],
369
                    until=self['until'],
370
                    show_only_shared=self['shared'])
371
                self.print_containers(r.json)
372
            else:
373
                prefix = self.path if self.path\
374
                else self['prefix']
375
                r = self.client.container_get(
376
                    limit=False if self['more'] else self['limit'],
377
                    marker=self['marker'],
378
                    prefix=prefix,
379
                    delimiter=self['delimiter'],
380
                    path=self['path'],
381
                    if_modified_since=self['if_modified_since'],
382
                    if_unmodified_since=self['if_unmodified_since'],
383
                    until=self['until'],
384
                    meta=self['meta'],
385
                    show_only_shared=self['shared'])
386
                self.print_objects(r.json)
387
        except ClientError as err:
388
            if err.status == 404:
389
                if 'container' in ('%s' % err).lower():
390
                    raiseCLIError(
391
                        err,
392
                        'No container %s in account %s'\
393
                        % (self.container, self.account),
394
                        details=self.generic_err_details)
395
                elif 'object' in ('%s' % err).lower():
396
                    raiseCLIError(
397
                        err,
398
                        'No object %s in %s\'s container %s'\
399
                        % (self.path, self.account, self.container),
400
                        details=self.generic_err_details)
401
            raise_connection_errors(err)
402
            raiseCLIError(err)
403
        except Exception as e:
404
            raiseCLIError(e)
405

    
406

    
407
@command(pithos_cmds)
408
class store_mkdir(_store_container_command):
409
    """Create a directory
410
    """
411

    
412
    __doc__ += '\n. '.join(about_directories)
413

    
414
    def main(self, container___directory):
415
        super(self.__class__,
416
            self).main(container___directory, path_is_optional=False)
417
        try:
418
            self.client.create_directory(self.path)
419
        except ClientError as err:
420
            if err.status == 404:
421
                if 'container' in ('%s' % err).lower():
422
                    raiseCLIError(
423
                        err,
424
                        'No container %s in account %s'\
425
                        % (self.container, self.account),
426
                        details=self.generic_err_details)
427
                elif 'object' in ('%s' % err).lower():
428
                    raiseCLIError(
429
                        err,
430
                        'No object %s in container %s'\
431
                        % (self.path, self.container),
432
                        details=self.generic_err_details)
433
            raise_connection_errors(err)
434
            raiseCLIError(err)
435
        except Exception as err:
436
            raiseCLIError(err)
437

    
438

    
439
@command(pithos_cmds)
440
class store_touch(_store_container_command):
441
    """Create an empty object (file)
442
    If object exists, this command will reset it to 0 length
443
    """
444

    
445
    arguments = dict(
446
        content_type=ValueArgument(
447
            'Set content type (default: application/octet-stream)',
448
            '--content-type',
449
            default='application/octet-stream')
450
    )
451

    
452
    def main(self, container___path):
453
        super(store_touch, self).main(container___path)
454
        try:
455
            self.client.create_object(self.path, self['content_type'])
456
        except ClientError as err:
457
            if err.status == 404:
458
                if 'container' in ('%s' % err).lower():
459
                    raiseCLIError(
460
                        err,
461
                        'No container %s in account %s'\
462
                        % (self.container, self.account),
463
                        details=self.generic_err_details)
464
                elif 'object' in ('%s' % err).lower():
465
                    raiseCLIError(
466
                        err,
467
                        'No object %s in container %s'\
468
                        % (self.path, self.container),
469
                        details=self.generic_err_details)
470
            raise_connection_errors(err)
471
            raiseCLIError(err)
472
        except Exception as err:
473
            raiseCLIError(err)
474

    
475

    
476
@command(pithos_cmds)
477
class store_create(_store_account_command):
478
    """Create a container"""
479

    
480
    arguments = dict(
481
        versioning=ValueArgument(
482
            'set container versioning (auto/none)',
483
            '--versioning'),
484
        quota=IntArgument('set default container quota', '--quota'),
485
        meta=KeyValueArgument(
486
            'set container metadata (can be repeated)',
487
            '--meta')
488
    )
489

    
490
    def main(self, container):
491
        super(self.__class__, self).main(container)
492
        try:
493
            self.client.container_put(quota=self['quota'],
494
                versioning=self['versioning'],
495
                metadata=self['meta'])
496
        except ClientError as err:
497
            if err.status == 404:
498
                if 'container' in ('%s' % err).lower():
499
                    raiseCLIError(
500
                        err,
501
                        'No container %s in account %s'\
502
                        % (self.container, self.account),
503
                        details=self.generic_err_details)
504
                elif 'object' in ('%s' % err).lower():
505
                    raiseCLIError(
506
                        err,
507
                        'No object %s in container %s'\
508
                        % (self.path, self.container),
509
                        details=self.generic_err_details)
510
            raise_connection_errors(err)
511
            raiseCLIError(err)
512
        except Exception as e:
513
            raiseCLIError(e)
514

    
515

    
516
@command(pithos_cmds)
517
class store_copy(_store_container_command):
518
    """Copy an object from container to (another) container
519
    Use options:
520
    1. <container1>:<path1> [container2:]<path2> : if container2 is not given,
521
    destination is container1:path2
522
    2. <container>:<path1> <path2> : make a copy in the same container
523
    3. Use --container= instead of <container1>
524
    """
525

    
526
    arguments = dict(
527
        source_version=ValueArgument(
528
            'copy specific version',
529
            '--source-version'),
530
        public=ValueArgument('make object publicly accessible', '--public'),
531
        content_type=ValueArgument(
532
            'change object\'s content type',
533
            '--content-type'),
534
        recursive=FlagArgument(
535
            'mass copy with delimiter /',
536
            ('-r', '--recursive')),
537
    )
538

    
539
    def __init__(self, arguments={}):
540
        super(self.__class__, self).__init__(arguments)
541
        self['delimiter'] = DelimiterArgument(
542
            self,
543
            parsed_name='--delimiter',
544
            help=u'copy objects prefixed as src_object + delimiter')
545

    
546
    def main(self, source_container___path, destination_container____path__):
547
        super(self.__class__,
548
            self).main(source_container___path, path_is_optional=False)
549
        try:
550
            dst = destination_container____path__.split(':')
551
            (dst_cont, dst_path) = (dst[0], dst[1])\
552
            if len(dst) > 1 else (None, dst[0])
553
            self.client.copy_object(src_container=self.container,
554
                src_object=self.path,
555
                dst_container=dst_cont if dst_cont else self.container,
556
                dst_object=dst_path,
557
                source_version=self['source_version'],
558
                public=self['public'],
559
                content_type=self['content_type'],
560
                delimiter=self['delimiter'])
561
        except ClientError as err:
562
            if err.status == 404:
563
                if 'container' in ('%s' % err).lower():
564
                    cont_msg = '(either %s or %s)' % (
565
                        self.container,
566
                        dst_cont
567
                    ) if dst_cont else self.container
568
                    raiseCLIError(
569
                        err,
570
                        'No container %s in account %s'\
571
                        % (cont_msg, self.account),
572
                        details=self.generic_err_details)
573
                elif 'object' in ('%s' % err).lower():
574
                    raiseCLIError(
575
                        err,
576
                        'No object %s in container %s'\
577
                        % (self.path, self.container),
578
                        details=self.generic_err_details)
579
            raise_connection_errors(err)
580
            raiseCLIError(err)
581
        except Exception as e:
582
            raiseCLIError(e)
583

    
584

    
585
@command(pithos_cmds)
586
class store_move(_store_container_command):
587
    """Copy an object
588
    Use options:
589
    1. <container1>:<path1> [container2:]<path2> : if container2 not given,
590
    destination is container1:path2
591
    2. <container>:<path1> path2 : rename
592
    3. Use --container= instead of <container1>
593
    """
594

    
595
    arguments = dict(
596
        source_version=ValueArgument('specify version', '--source-version'),
597
        public=FlagArgument('make object publicly accessible', '--public'),
598
        content_type=ValueArgument('modify content type', '--content-type'),
599
        recursive=FlagArgument('up to delimiter /', ('-r', '--recursive'))
600
    )
601

    
602
    def __init__(self, arguments={}):
603
        super(self.__class__, self).__init__(arguments)
604
        self['delimiter'] = DelimiterArgument(
605
            self,
606
            parsed_name='--delimiter',
607
            help=u'move objects prefixed as src_object + delimiter')
608

    
609
    def main(self, source_container___path, destination_container____path__):
610
        super(self.__class__,
611
            self).main(source_container___path, path_is_optional=False)
612
        try:
613
            dst = destination_container____path__.split(':')
614
            (dst_cont, dst_path) = (dst[0], dst[1])\
615
            if len(dst) > 1 else (None, dst[0])
616
            self.client.move_object(src_container=self.container,
617
                src_object=self.path,
618
                dst_container=dst_cont if dst_cont else self.container,
619
                dst_object=dst_path,
620
                source_version=self['source_version'],
621
                public=self['public'],
622
                content_type=self['content_type'],
623
                delimiter=self['delimiter'])
624
        except ClientError as err:
625
            if err.status == 404:
626
                if 'container' in ('%s' % err).lower():
627
                    cont_msg = '(either %s or %s)' % (
628
                        self.container,
629
                        dst_cont
630
                    ) if dst_cont else self.container
631
                    raiseCLIError(
632
                        err,
633
                        'No container %s in account %s'\
634
                        % (cont_msg, self.account),
635
                        details=self.generic_err_details)
636
                elif 'object' in ('%s' % err).lower():
637
                    raiseCLIError(
638
                        err,
639
                        'No object %s in container %s'\
640
                        % (self.path, self.container),
641
                        details=self.generic_err_details)
642
            raise_connection_errors(err)
643
            raiseCLIError(err)
644
        except Exception as e:
645
            raiseCLIError(e)
646

    
647

    
648
@command(pithos_cmds)
649
class store_append(_store_container_command):
650
    """Append local file to (existing) remote object"""
651

    
652
    arguments = dict(
653
        progress_bar=ProgressBarArgument(
654
            'do not show progress bar',
655
            '--no-progress-bar',
656
            default=False)
657
    )
658

    
659
    def main(self, local_path, container___path):
660
        super(self.__class__,
661
            self).main(container___path, path_is_optional=False)
662
        try:
663
            f = open(local_path, 'rb')
664
            progress_bar = self.arguments['progress_bar']
665
            try:
666
                upload_cb = progress_bar.get_generator('Appending blocks')
667
            except Exception:
668
                upload_cb = None
669
            self.client.append_object(self.path, f, upload_cb)
670
        except ClientError as err:
671
            progress_bar.finish()
672
            if err.status == 404:
673
                if 'container' in ('%s' % err).lower():
674
                    raiseCLIError(
675
                        err,
676
                        'No container %s in account %s'\
677
                        % (self.container, self.account),
678
                        details=self.generic_err_details)
679
                elif 'object' in ('%s' % err).lower():
680
                    raiseCLIError(
681
                        err,
682
                        'No object %s in container %s'\
683
                        % (self.path, self.container),
684
                        details=self.generic_err_details)
685
            raise_connection_errors(err)
686
            raiseCLIError(err)
687
        except Exception as e:
688
            progress_bar.finish()
689
            raiseCLIError(e)
690
        finally:
691
            progress_bar.finish()
692

    
693

    
694
@command(pithos_cmds)
695
class store_truncate(_store_container_command):
696
    """Truncate remote file up to a size"""
697

    
698
    def main(self, container___path, size=0):
699
        super(self.__class__, self).main(container___path)
700
        try:
701
            self.client.truncate_object(self.path, size)
702
        except ClientError as err:
703
            if err.status == 404:
704
                if 'container' in ('%s' % err).lower():
705
                    raiseCLIError(
706
                        err,
707
                        'No container %s in account %s'\
708
                        % (self.container, self.account),
709
                        details=self.generic_err_details)
710
                elif 'object' in ('%s' % err).lower():
711
                    raiseCLIError(
712
                        err,
713
                        'No object %s in container %s'\
714
                        % (self.path, self.container),
715
                        details=self.generic_err_details)
716
            if err.status == 400 and\
717
                'object length is smaller than range length'\
718
                in ('%s' % err).lower():
719
                raiseCLIError(err, 'Object %s:%s <= %sb' % (
720
                    self.container,
721
                    self.path,
722
                    size))
723
            raise_connection_errors(err)
724
            raiseCLIError(err)
725
        except Exception as e:
726
            raiseCLIError(e)
727

    
728

    
729
@command(pithos_cmds)
730
class store_overwrite(_store_container_command):
731
    """Overwrite part (from start to end) of a remote file"""
732

    
733
    arguments = dict(
734
        progress_bar=ProgressBarArgument(
735
            'do not show progress bar',
736
            '--no-progress-bar',
737
            default=False)
738
    )
739

    
740
    def main(self, local_path, container___path, start, end):
741
        super(self.__class__,
742
            self).main(container___path, path_is_optional=True)
743
        try:
744
            progress_bar = self.arguments['progress_bar']
745
            f = open(local_path, 'rb')
746
            try:
747
                upload_cb = progress_bar.get_generator('Overwritting blocks')
748
            except Exception:
749
                upload_cb = None
750
            self.path = self.path if self.path else path.basename(local_path)
751
            self.client.overwrite_object(
752
                obj=self.path,
753
                start=start,
754
                end=end,
755
                source_file=f,
756
                upload_cb=upload_cb)
757
        except ClientError as err:
758
            progress_bar.finish()
759
            if err.status == 404:
760
                if 'container' in ('%s' % err).lower():
761
                    raiseCLIError(
762
                        err,
763
                        'No container %s in account %s'\
764
                        % (self.container, self.account),
765
                        details=self.generic_err_details)
766
                elif 'object' in ('%s' % err).lower():
767
                    raiseCLIError(
768
                        err,
769
                        'No object %s in container %s'\
770
                        % (self.path, self.container),
771
                        details=self.generic_err_details)
772
            raise_connection_errors(err)
773
            raiseCLIError(err)
774
        except Exception as e:
775
            progress_bar.finish()
776
            raiseCLIError(e)
777
        finally:
778
            progress_bar.finish()
779

    
780

    
781
@command(pithos_cmds)
782
class store_manifest(_store_container_command):
783
    """Create a remote file of uploaded parts by manifestation
784
    How to use manifest:
785
    - upload parts of the file on a with names X.1, X.2, ...
786
    - /store manifest X
787
    An empty object X will behave as the result file of the
788
    union of X.1, X.2, ...
789
    """
790

    
791
    arguments = dict(
792
        etag=ValueArgument('check written data', '--etag'),
793
        content_encoding=ValueArgument(
794
            'set MIME content type',
795
            '--content-encoding'),
796
        content_disposition=ValueArgument(
797
            'the presentation style of the object',
798
            '--content-disposition'),
799
        content_type=ValueArgument(
800
            'specify content type',
801
            '--content-type',
802
            default='application/octet-stream'),
803
        sharing=SharingArgument(
804
            'define object sharing policy \n' +\
805
            '    ( "read=user1,grp1,user2,... write=user1,grp2,..." )',
806
            '--sharing'),
807
        public=FlagArgument('make object publicly accessible', '--public')
808
    )
809

    
810
    def main(self, container___path):
811
        super(self.__class__,
812
            self).main(container___path, path_is_optional=False)
813
        try:
814
            self.client.create_object_by_manifestation(
815
                self.path,
816
                content_encoding=self['content_encoding'],
817
                content_disposition=self['content_disposition'],
818
                content_type=self['content_type'],
819
                sharing=self['sharing'],
820
                public=self['public'])
821
        except ClientError as err:
822
            if err.status == 404:
823
                if 'container' in ('%s' % err).lower():
824
                    raiseCLIError(
825
                        err,
826
                        'No container %s in account %s'\
827
                        % (self.container, self.account),
828
                        details=self.generic_err_details)
829
                elif 'object' in ('%s' % err).lower():
830
                    raiseCLIError(
831
                        err,
832
                        'No object %s in container %s'\
833
                        % (self.path, self.container),
834
                        details=self.generic_err_details)
835
            raise_connection_errors(err)
836
            raiseCLIError(err)
837
        except Exception as e:
838
            raiseCLIError(e)
839

    
840

    
841
@command(pithos_cmds)
842
class store_upload(_store_container_command):
843
    """Upload a file"""
844

    
845
    arguments = dict(
846
        use_hashes=FlagArgument(
847
            'provide hashmap file instead of data',
848
            '--use-hashes'),
849
        etag=ValueArgument('check written data', '--etag'),
850
        unchunked=FlagArgument('avoid chunked transfer mode', '--unchunked'),
851
        content_encoding=ValueArgument(
852
            'set MIME content type',
853
            '--content-encoding'),
854
        content_disposition=ValueArgument(
855
            'specify objects presentation style',
856
            '--content-disposition'),
857
        content_type=ValueArgument('specify content type', '--content-type'),
858
        sharing=SharingArgument(
859
            help='define sharing object policy \n' +\
860
            '( "read=user1,grp1,user2,... write=user1,grp2,... )',
861
            parsed_name='--sharing'),
862
        public=FlagArgument('make object publicly accessible', '--public'),
863
        poolsize=IntArgument('set pool size', '--with-pool-size'),
864
        progress_bar=ProgressBarArgument(
865
            'do not show progress bar',
866
            '--no-progress-bar',
867
            default=False)
868
    )
869

    
870
    def main(self, local_path, container____path__):
871
        super(self.__class__, self).main(container____path__)
872
        remote_path = self.path if self.path else local_path
873
        poolsize = self['poolsize']
874
        if poolsize is not None:
875
            self.client.POOL_SIZE = int(poolsize)
876
        params = dict(content_encoding=self['content_encoding'],
877
            content_type=self['content_type'],
878
            content_disposition=self['content_disposition'],
879
            sharing=self['sharing'],
880
            public=self['public'])
881
        try:
882
            progress_bar = self.arguments['progress_bar']
883
            hash_bar = progress_bar.clone()
884
            with open(local_path, 'rb') as f:
885
                if self['unchunked']:
886
                    self.client.upload_object_unchunked(
887
                        remote_path,
888
                        f,
889
                        etag=self['etag'],
890
                        withHashFile=self['use_hashes'],
891
                        **params)
892
                else:
893
                    hash_cb = hash_bar.get_generator(
894
                        'Calculating block hashes')
895
                    upload_cb = progress_bar.get_generator('Uploading')
896
                    self.client.upload_object(
897
                        remote_path,
898
                        f,
899
                        hash_cb=hash_cb,
900
                        upload_cb=upload_cb,
901
                        **params)
902
                    progress_bar.finish()
903
                    hash_bar.finish()
904
        except ClientError as err:
905
            progress_bar.finish()
906
            hash_bar.finish()
907
            raiseCLIError(err, '"%s" not accessible' % container____path__)
908
        except IOError as err:
909
            progress_bar.finish()
910
            hash_bar.finish()
911
            raiseCLIError(err, 'Failed to read form file %s' % local_path, 2)
912
        print 'Upload completed'
913

    
914

    
915
@command(pithos_cmds)
916
class store_cat(_store_container_command):
917
    """Print a file to console"""
918

    
919
    arguments = dict(
920
        range=RangeArgument('show range of data', '--range'),
921
        if_match=ValueArgument('show output if ETags match', '--if-match'),
922
        if_none_match=ValueArgument(
923
            'show output if ETags match',
924
            '--if-none-match'),
925
        if_modified_since=DateArgument(
926
            'show output modified since then',
927
            '--if-modified-since'),
928
        if_unmodified_since=DateArgument(
929
            'show output unmodified since then',
930
            '--if-unmodified-since'),
931
        object_version=ValueArgument(
932
            'get the specific version',
933
            '--object-version')
934
    )
935

    
936
    def main(self, container___path):
937
        super(self.__class__,
938
            self).main(container___path, path_is_optional=False)
939
        try:
940
            self.client.download_object(self.path, stdout,
941
            range=self['range'],
942
            version=self['object_version'],
943
            if_match=self['if_match'],
944
            if_none_match=self['if_none_match'],
945
            if_modified_since=self['if_modified_since'],
946
            if_unmodified_since=self['if_unmodified_since'])
947
        except ClientError as err:
948
            raiseCLIError(err)
949

    
950

    
951
@command(pithos_cmds)
952
class store_download(_store_container_command):
953
    """Download a file"""
954

    
955
    arguments = dict(
956
        resume=FlagArgument('Resume instead of overwrite', '--resume'),
957
        range=RangeArgument('show range of data', '--range'),
958
        if_match=ValueArgument('show output if ETags match', '--if-match'),
959
        if_none_match=ValueArgument(
960
            'show output if ETags match',
961
            '--if-none-match'),
962
        if_modified_since=DateArgument(
963
            'show output modified since then',
964
            '--if-modified-since'),
965
        if_unmodified_since=DateArgument(
966
            'show output unmodified since then',
967
            '--if-unmodified-since'),
968
        object_version=ValueArgument(
969
            'get the specific version',
970
            '--object-version'),
971
        poolsize=IntArgument('set pool size', '--with-pool-size'),
972
        progress_bar=ProgressBarArgument(
973
            'do not show progress bar',
974
            '--no-progress-bar',
975
            default=False)
976
    )
977

    
978
    def main(self, container___path, local_path):
979
        super(self.__class__,
980
            self).main(container___path, path_is_optional=False)
981

    
982
        # setup output stream
983
        if local_path is None:
984
            out = stdout
985
        else:
986
            try:
987
                if self['resume']:
988
                    out = open(local_path, 'rwb+')
989
                else:
990
                    out = open(local_path, 'wb+')
991
            except IOError as err:
992
                raiseCLIError(err, 'Cannot write to file %s' % local_path, 1)
993
        poolsize = self['poolsize']
994
        if poolsize is not None:
995
            self.client.POOL_SIZE = int(poolsize)
996

    
997
        try:
998
            progress_bar = self.arguments['progress_bar']
999
            download_cb = progress_bar.get_generator('Downloading')
1000
            self.client.download_object(self.path, out,
1001
                download_cb=download_cb,
1002
                range=self['range'],
1003
                version=self['object_version'],
1004
                if_match=self['if_match'],
1005
                resume=self['resume'],
1006
                if_none_match=self['if_none_match'],
1007
                if_modified_since=self['if_modified_since'],
1008
                if_unmodified_since=self['if_unmodified_since'])
1009
            progress_bar.finish()
1010
        except ClientError as err:
1011
            progress_bar.finish()
1012
            raiseCLIError(err)
1013
        except KeyboardInterrupt:
1014
            from threading import enumerate as activethreads
1015
            stdout.write('\nFinishing active threads ')
1016
            for thread in activethreads():
1017
                stdout.flush()
1018
                try:
1019
                    thread.join()
1020
                    stdout.write('.')
1021
                except RuntimeError:
1022
                    continue
1023
            progress_bar.finish()
1024
            print('\ndownload canceled by user')
1025
            if local_path is not None:
1026
                print('to resume, re-run with --resume')
1027
        except Exception as e:
1028
            progress_bar.finish()
1029
            raiseCLIError(e)
1030
        print
1031

    
1032

    
1033
@command(pithos_cmds)
1034
class store_hashmap(_store_container_command):
1035
    """Get the hashmap of an object"""
1036

    
1037
    arguments = dict(
1038
        if_match=ValueArgument('show output if ETags match', '--if-match'),
1039
        if_none_match=ValueArgument(
1040
            'show output if ETags match',
1041
            '--if-none-match'),
1042
        if_modified_since=DateArgument(
1043
            'show output modified since then',
1044
            '--if-modified-since'),
1045
        if_unmodified_since=DateArgument(
1046
            'show output unmodified since then',
1047
            '--if-unmodified-since'),
1048
        object_version=ValueArgument(
1049
            'get the specific version',
1050
            '--object-version')
1051
    )
1052

    
1053
    def main(self, container___path):
1054
        super(self.__class__,
1055
            self).main(container___path, path_is_optional=False)
1056
        try:
1057
            data = self.client.get_object_hashmap(
1058
                self.path,
1059
                version=self['object_version'],
1060
                if_match=self['if_match'],
1061
                if_none_match=self['if_none_match'],
1062
                if_modified_since=self['if_modified_since'],
1063
                if_unmodified_since=self['if_unmodified_since'])
1064
        except ClientError as err:
1065
            raiseCLIError(err)
1066
        print_dict(data)
1067

    
1068

    
1069
@command(pithos_cmds)
1070
class store_delete(_store_container_command):
1071
    """Delete a container [or an object]"""
1072

    
1073
    arguments = dict(
1074
        until=DateArgument('remove history until that date', '--until'),
1075
        recursive=FlagArgument(
1076
            'empty dir or container and delete (if dir)',
1077
            ('-r', '--recursive'))
1078
    )
1079

    
1080
    def __init__(self, arguments={}):
1081
        super(self.__class__, self).__init__(arguments)
1082
        self['delimiter'] = DelimiterArgument(
1083
            self,
1084
            parsed_name='--delimiter',
1085
            help='delete objects prefixed with <object><delimiter>')
1086

    
1087
    def main(self, container____path__):
1088
        super(self.__class__, self).main(container____path__)
1089
        try:
1090
            if self.path is None:
1091
                self.client.del_container(
1092
                    until=self['until'],
1093
                    delimiter=self['delimiter'])
1094
            else:
1095
                # self.client.delete_object(self.path)
1096
                self.client.del_object(
1097
                    self.path,
1098
                    until=self['until'],
1099
                    delimiter=self['delimiter'])
1100
        except ClientError as err:
1101
            raiseCLIError(err)
1102

    
1103

    
1104
@command(pithos_cmds)
1105
class store_purge(_store_container_command):
1106
    """Purge a container
1107
    To completely erase a container:
1108
    .   /store delete -r <container>
1109
    .   /store purge <container
1110
    """
1111

    
1112
    def main(self, container):
1113
        super(self.__class__, self).main(container)
1114
        try:
1115
            self.client.purge_container()
1116
        except ClientError as err:
1117
            raiseCLIError(err)
1118

    
1119

    
1120
@command(pithos_cmds)
1121
class store_publish(_store_container_command):
1122
    """Publish the object and print the public url"""
1123

    
1124
    def main(self, container___path):
1125
        super(self.__class__,
1126
            self).main(container___path, path_is_optional=False)
1127
        try:
1128
            url = self.client.publish_object(self.path)
1129
        except ClientError as err:
1130
            raiseCLIError(err)
1131
        print(url)
1132

    
1133

    
1134
@command(pithos_cmds)
1135
class store_unpublish(_store_container_command):
1136
    """Unpublish an object"""
1137

    
1138
    def main(self, container___path):
1139
        super(self.__class__,
1140
            self).main(container___path, path_is_optional=False)
1141
        try:
1142
            self.client.unpublish_object(self.path)
1143
        except ClientError as err:
1144
            raiseCLIError(err)
1145

    
1146

    
1147
@command(pithos_cmds)
1148
class store_permissions(_store_container_command):
1149
    """Get object read / write permissions """
1150

    
1151
    def main(self, container___path):
1152
        super(self.__class__,
1153
            self).main(container___path, path_is_optional=False)
1154
        try:
1155
            reply = self.client.get_object_sharing(self.path)
1156
            print_dict(reply)
1157
        except ClientError as err:
1158
            raiseCLIError(err)
1159

    
1160

    
1161
@command(pithos_cmds)
1162
class store_setpermissions(_store_container_command):
1163
    """Set sharing permissions """
1164

    
1165
    def format_permition_dict(self, permissions):
1166
        read = False
1167
        write = False
1168
        for perms in permissions:
1169
            splstr = perms.split('=')
1170
            if 'read' == splstr[0]:
1171
                read = [user_or_group.strip() \
1172
                for user_or_group in splstr[1].split(',')]
1173
            elif 'write' == splstr[0]:
1174
                write = [user_or_group.strip() \
1175
                for user_or_group in splstr[1].split(',')]
1176
            else:
1177
                read = False
1178
                write = False
1179
        if not read and not write:
1180
            raiseCLIError(None,
1181
            'Usage:\tread=<groups,users> write=<groups,users>')
1182
        return (read, write)
1183

    
1184
    def main(self, container___path, *permissions):
1185
        super(self.__class__,
1186
            self).main(container___path, path_is_optional=False)
1187
        (read, write) = self.format_permition_dict(permissions)
1188
        try:
1189
            self.client.set_object_sharing(self.path,
1190
                read_permition=read, write_permition=write)
1191
        except ClientError as err:
1192
            raiseCLIError(err)
1193

    
1194

    
1195
@command(pithos_cmds)
1196
class store_delpermissions(_store_container_command):
1197
    """Delete all sharing permissions"""
1198

    
1199
    def main(self, container___path):
1200
        super(self.__class__,
1201
            self).main(container___path, path_is_optional=False)
1202
        try:
1203
            self.client.del_object_sharing(self.path)
1204
        except ClientError as err:
1205
            raiseCLIError(err)
1206

    
1207

    
1208
@command(pithos_cmds)
1209
class store_info(_store_container_command):
1210
    """Get information for account [, container [or object]]"""
1211

    
1212
    arguments = dict(
1213
        object_version=ValueArgument(
1214
            'show specific version \ (applies only for objects)',
1215
            '--object-version')
1216
    )
1217

    
1218
    def main(self, container____path__=None):
1219
        super(self.__class__, self).main(container____path__)
1220
        try:
1221
            if self.container is None:
1222
                reply = self.client.get_account_info()
1223
            elif self.path is None:
1224
                reply = self.client.get_container_info(self.container)
1225
            else:
1226
                reply = self.client.get_object_info(
1227
                    self.path,
1228
                    version=self['object_version'])
1229
        except ClientError as err:
1230
            raiseCLIError(err)
1231
        print_dict(reply)
1232

    
1233

    
1234
@command(pithos_cmds)
1235
class store_meta(_store_container_command):
1236
    """Get custom meta-content for account [, container [or object]]"""
1237

    
1238
    arguments = dict(
1239
        detail=FlagArgument('show detailed output', '-l'),
1240
        until=DateArgument('show metadata until then', '--until'),
1241
        object_version=ValueArgument(
1242
            'show specific version \ (applies only for objects)',
1243
            '--object-version')
1244
    )
1245

    
1246
    def main(self, container____path__=None):
1247
        super(self.__class__, self).main(container____path__)
1248

    
1249
        detail = self['detail']
1250
        try:
1251
            until = self['until']
1252
            if self.container is None:
1253
                print(bold(self.client.account))
1254
                if detail:
1255
                    reply = self.client.get_account_info(until=until)
1256
                else:
1257
                    reply = self.client.get_account_meta(until=until)
1258
                    reply = pretty_keys(reply, '-')
1259
            elif self.path is None:
1260
                print(bold('%s: %s' % (self.client.account, self.container)))
1261
                if detail:
1262
                    reply = self.client.get_container_info(until=until)
1263
                else:
1264
                    cmeta = self.client.get_container_meta(until=until)
1265
                    ometa = self.client.get_container_object_meta(until=until)
1266
                    reply = {'container-meta': pretty_keys(cmeta, '-'),
1267
                        'object-meta': pretty_keys(ometa, '-')}
1268
            else:
1269
                print(bold('%s: %s:%s'\
1270
                    % (self.client.account, self.container, self.path)))
1271
                version = self['object_version']
1272
                if detail:
1273
                    reply = self.client.get_object_info(self.path,
1274
                        version=version)
1275
                else:
1276
                    reply = self.client.get_object_meta(self.path,
1277
                        version=version)
1278
                    reply = pretty_keys(pretty_keys(reply, '-'))
1279
        except ClientError as err:
1280
            raiseCLIError(err)
1281
        print_dict(reply)
1282

    
1283

    
1284
@command(pithos_cmds)
1285
class store_setmeta(_store_container_command):
1286
    """Set a new metadatum for account [, container [or object]]"""
1287

    
1288
    def main(self, metakey___metaval, container____path__=None):
1289
        super(self.__class__, self).main(container____path__)
1290
        try:
1291
            metakey, metavalue = metakey___metaval.split(':')
1292
        except ValueError as err:
1293
            raiseCLIError(err, 'Usage:  metakey:metavalue', importance=1)
1294
        try:
1295
            if self.container is None:
1296
                self.client.set_account_meta({metakey: metavalue})
1297
            elif self.path is None:
1298
                self.client.set_container_meta({metakey: metavalue})
1299
            else:
1300
                self.client.set_object_meta(self.path, {metakey: metavalue})
1301
        except ClientError as err:
1302
            raiseCLIError(err)
1303

    
1304

    
1305
@command(pithos_cmds)
1306
class store_delmeta(_store_container_command):
1307
    """Delete an existing metadatum of account [, container [or object]]"""
1308

    
1309
    def main(self, metakey, container____path__=None):
1310
        super(self.__class__, self).main(container____path__)
1311
        try:
1312
            if self.container is None:
1313
                self.client.del_account_meta(metakey)
1314
            elif self.path is None:
1315
                self.client.del_container_meta(metakey)
1316
            else:
1317
                self.client.del_object_meta(self.path, metakey)
1318
        except ClientError as err:
1319
            raiseCLIError(err)
1320

    
1321

    
1322
@command(pithos_cmds)
1323
class store_quota(_store_account_command):
1324
    """Get  quota for account [or container]"""
1325

    
1326
    def main(self, container=None):
1327
        super(self.__class__, self).main()
1328
        try:
1329
            if container is None:
1330
                reply = self.client.get_account_quota()
1331
            else:
1332
                reply = self.client.get_container_quota(container)
1333
        except ClientError as err:
1334
            raiseCLIError(err)
1335
        print_dict(reply)
1336

    
1337

    
1338
@command(pithos_cmds)
1339
class store_setquota(_store_account_command):
1340
    """Set new quota (in KB) for account [or container]"""
1341

    
1342
    def main(self, quota, container=None):
1343
        super(self.__class__, self).main()
1344
        try:
1345
            if container is None:
1346
                self.client.set_account_quota(quota)
1347
            else:
1348
                self.client.container = container
1349
                self.client.set_container_quota(quota)
1350
        except ClientError as err:
1351
            raiseCLIError(err)
1352

    
1353

    
1354
@command(pithos_cmds)
1355
class store_versioning(_store_account_command):
1356
    """Get  versioning for account [or container ]"""
1357

    
1358
    def main(self, container=None):
1359
        super(self.__class__, self).main()
1360
        try:
1361
            if container is None:
1362
                reply = self.client.get_account_versioning()
1363
            else:
1364
                reply = self.client.get_container_versioning(container)
1365
        except ClientError as err:
1366
            raiseCLIError(err)
1367
        print_dict(reply)
1368

    
1369

    
1370
@command(pithos_cmds)
1371
class store_setversioning(_store_account_command):
1372
    """Set new versioning (auto, none) for account [or container]"""
1373

    
1374
    def main(self, versioning, container=None):
1375
        super(self.__class__, self).main()
1376
        try:
1377
            if container is None:
1378
                self.client.set_account_versioning(versioning)
1379
            else:
1380
                self.client.container = container
1381
                self.client.set_container_versioning(versioning)
1382
        except ClientError as err:
1383
            raiseCLIError(err)
1384

    
1385

    
1386
@command(pithos_cmds)
1387
class store_group(_store_account_command):
1388
    """Get user groups details for account"""
1389

    
1390
    def main(self):
1391
        super(self.__class__, self).main()
1392
        try:
1393
            reply = self.client.get_account_group()
1394
        except ClientError as err:
1395
            raiseCLIError(err)
1396
        print_dict(reply)
1397

    
1398

    
1399
@command(pithos_cmds)
1400
class store_setgroup(_store_account_command):
1401
    """Create/update a new user group on account"""
1402

    
1403
    def main(self, groupname, *users):
1404
        super(self.__class__, self).main()
1405
        try:
1406
            self.client.set_account_group(groupname, users)
1407
        except ClientError as err:
1408
            raiseCLIError(err)
1409

    
1410

    
1411
@command(pithos_cmds)
1412
class store_delgroup(_store_account_command):
1413
    """Delete a user group on an account"""
1414

    
1415
    def main(self, groupname):
1416
        super(self.__class__, self).main()
1417
        try:
1418
            self.client.del_account_group(groupname)
1419
        except ClientError as err:
1420
            raiseCLIError(err)
1421

    
1422

    
1423
@command(pithos_cmds)
1424
class store_sharers(_store_account_command):
1425
    """List the accounts that share objects with default account"""
1426

    
1427
    arguments = dict(
1428
        detail=FlagArgument('show detailed output', '-l'),
1429
        marker=ValueArgument('show output greater then marker', '--marker')
1430
    )
1431

    
1432
    def main(self):
1433
        super(self.__class__, self).main()
1434
        try:
1435
            marker = self['marker']
1436
            accounts = self.client.get_sharing_accounts(marker=marker)
1437
        except ClientError as err:
1438
            raiseCLIError(err)
1439

    
1440
        for acc in accounts:
1441
            stdout.write(bold(acc['name']) + ' ')
1442
            if self['detail']:
1443
                print_dict(acc, exclude='name', ident=4)
1444
        if not self['detail']:
1445
            print
1446

    
1447

    
1448
@command(pithos_cmds)
1449
class store_versions(_store_container_command):
1450
    """Get the version list of an object"""
1451

    
1452
    def main(self, container___path):
1453
        super(store_versions, self).main(container___path)
1454
        try:
1455
            versions = self.client.get_object_versionlist(self.path)
1456
        except ClientError as err:
1457
            raiseCLIError(err)
1458

    
1459
        print('%s:%s versions' % (self.container, self.path))
1460
        for vitem in versions:
1461
            t = localtime(float(vitem[1]))
1462
            vid = bold(unicode(vitem[0]))
1463
            print('\t%s \t(%s)' % (vid, strftime('%d-%m-%Y %H:%M:%S', t)))
1464

    
1465