Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 451a7992

History | View | Annotate | Download (46.6 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
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 datetime import datetime as dtm
47
from logging import getLogger
48

    
49
kloger = getLogger('kamaki')
50

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

    
54

    
55
# Argument functionality
56

    
57

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

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

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

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

    
78

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

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

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

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

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

    
118

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

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

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

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

    
140

    
141
class DateArgument(ValueArgument):
142
    """
143
    :value type: a string formated in an acceptable date format
144

145
    :value returns: same date in first of DATE_FORMATS
146
    """
147

    
148
    DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
149
        "%A, %d-%b-%y %H:%M:%S GMT",
150
        "%a, %d %b %Y %H:%M:%S GMT"]
151

    
152
    INPUT_FORMATS = DATE_FORMATS + ["%d-%m-%Y", "%H:%M:%S %d-%m-%Y"]
153

    
154
    @property
155
    def value(self):
156
        return getattr(self, '_value', self.default)
157

    
158
    @value.setter
159
    def value(self, newvalue):
160
        if newvalue is None:
161
            return
162
        self._value = self.format_date(newvalue)
163

    
164
    def format_date(self, datestr):
165
        for format in self.INPUT_FORMATS:
166
            try:
167
                t = dtm.strptime(datestr, format)
168
            except ValueError:
169
                continue
170
            self._value = t.strftime(self.DATE_FORMATS[0])
171
            return
172
        raiseCLIError(None,
173
            'Date Argument Error',
174
            details='%s not a valid date. correct formats:\n\t%s'\
175
            % (datestr, self.INPUT_FORMATS))
176

    
177

    
178
# Command specs
179

    
180

    
181
class _pithos_init(_command_init):
182
    """Initialize a pithos+ kamaki client"""
183

    
184
    def main(self):
185
        self.token = self.config.get('store', 'token')\
186
            or self.config.get('global', 'token')
187
        self.base_url = self.config.get('store', 'url')\
188
            or self.config.get('global', 'url')
189
        self.account = self.config.get('store', 'account')\
190
            or self.config.get('global', 'account')
191
        self.container = self.config.get('store', 'container')\
192
            or self.config.get('global', 'container')
193
        self.client = PithosClient(base_url=self.base_url,
194
            token=self.token,
195
            account=self.account,
196
            container=self.container)
197

    
198

    
199
class _store_account_command(_pithos_init):
200
    """Base class for account level storage commands"""
201

    
202
    def __init__(self, arguments={}):
203
        super(_store_account_command, self).__init__(arguments)
204
        self.arguments['account'] =\
205
            ValueArgument('Specify the account', '--account')
206

    
207
    def generator(self, message):
208
        return None
209

    
210
    def main(self):
211
        super(_store_account_command, self).main()
212
        if self.arguments['account'].value is not None:
213
            self.client.account = self.arguments['account'].value
214

    
215

    
216
class _store_container_command(_store_account_command):
217
    """Base class for container level storage commands"""
218

    
219
    def __init__(self, arguments={}):
220
        super(_store_container_command, self).__init__(arguments)
221
        self.arguments['container'] =\
222
            ValueArgument('Specify the container name', '--container')
223
        self.container = None
224
        self.path = None
225

    
226
    def extract_container_and_path(self,
227
        container_with_path,
228
        path_is_optional=True):
229
        try:
230
            assert isinstance(container_with_path, str)
231
        except AssertionError as err:
232
            raiseCLIError(err)
233

    
234
        cont, sep, path = container_with_path.partition(':')
235

    
236
        if sep:
237
            if not cont:
238
                raiseCLIError(None, 'Container is missing\n', importance=1)
239
            alt_cont = self.get_argument('container')
240
            if alt_cont and cont != alt_cont:
241
                raiseCLIError(None,
242
                    'Conflict: 2 containers (%s, %s)' % (cont, alt_cont),
243
                    importance=1)
244
            self.container = cont
245
            if not path:
246
                raiseCLIError(None,
247
                    'Path is missing for object in container %s' % cont,
248
                    importance=1,
249
                    details='Usage: <container>:<object path>')
250
            self.path = path
251
        else:
252
            alt_cont = self.get_argument('container') or self.client.container
253
            if alt_cont:
254
                self.container = alt_cont
255
                self.path = cont
256
            elif path_is_optional:
257
                self.container = cont
258
                self.path = None
259
            else:
260
                self.container = cont
261
                raiseCLIError(CLISyntaxError(
262
                    'Syntax error: container and path are both required',
263
                    importance=1,
264
                    details='Usage: <container>:<object path>'))
265

    
266
    def main(self, container_with_path=None, path_is_optional=True):
267
        super(_store_container_command, self).main()
268
        if container_with_path is not None:
269
            self.extract_container_and_path(container_with_path,
270
                path_is_optional)
271
            self.client.container = self.container
272
        elif self.get_argument('container') is not None:
273
            self.client.container = self.get_argument('container')
274
        self.container = self.client.container
275

    
276

    
277
@command(pithos_cmds)
278
class store_list(_store_container_command):
279
    """List containers, object trees or objects in a directory
280
    """
281

    
282
    def __init__(self, arguments={}):
283
        super(store_list, self).__init__(arguments)
284
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
285
        self.arguments['show_size'] =\
286
            ValueArgument('print output in chunks of size N', '-N')
287
        self.arguments['limit'] = IntArgument('show limited output', '-n')
288
        self.arguments['marker'] =\
289
            ValueArgument('show output greater that marker', '--marker')
290
        self.arguments['prefix'] =\
291
            ValueArgument('show output staritng with prefix', '--prefix')
292
        self.arguments['delimiter'] =\
293
            ValueArgument('show output up to delimiter', '--delimiter')
294
        self.arguments['path'] =\
295
            ValueArgument('show output starting with prefix up to /', '--path')
296
        self.arguments['meta'] =\
297
            ValueArgument('show output haviung the specified meta keys',
298
            '--meta', default=[])
299
        self.arguments['if_modified_since'] =\
300
            ValueArgument('show output modified since then',
301
                '--if-modified-since')
302
        self.arguments['if_unmodified_since'] =\
303
            ValueArgument('show output not modified since then',
304
            '--if-unmodified-since')
305
        self.arguments['until'] =\
306
            DateArgument('show metadata until then', '--until')
307
        self.arguments['format'] =\
308
            ValueArgument('format to parse until data (default: d/m/Y H:M:S',
309
            '--format')
310
        self.arguments['shared'] = FlagArgument('show only shared', '--shared')
311
        self.arguments['public'] = FlagArgument('show only public', '--public')
312

    
313
    def print_objects(self, object_list):
314
        import sys
315
        try:
316
            limit = self.get_argument('show_size')
317
            limit = int(limit)
318
        except (AttributeError, TypeError):
319
            limit = len(object_list) + 1
320
        #index = 0
321
        for index, obj in enumerate(object_list):
322
            if 'content_type' not in obj:
323
                continue
324
            pretty_obj = obj.copy()
325
            index += 1
326
            empty_space = ' ' * (len(str(len(object_list))) - len(str(index)))
327
            if obj['content_type'] == 'application/directory':
328
                isDir = True
329
                size = 'D'
330
            else:
331
                isDir = False
332
                size = format_size(obj['bytes'])
333
                pretty_obj['bytes'] = '%s (%s)' % (obj['bytes'], size)
334
            oname = bold(obj['name'])
335
            if self.get_argument('detail'):
336
                print('%s%s. %s' % (empty_space, index, oname))
337
                print_dict(pretty_keys(pretty_obj), exclude=('name'))
338
                print
339
            else:
340
                oname = '%s%s. %6s %s' % (empty_space, index, size, oname)
341
                oname += '/' if isDir else ''
342
                print(oname)
343
            if limit <= index < len(object_list) and index % limit == 0:
344
                print('(press "enter" to continue)')
345
                sys.stdin.read(1)
346

    
347
    def print_containers(self, container_list):
348
        import sys
349
        try:
350
            limit = self.get_argument('show_size')
351
            limit = int(limit)
352
        except (AttributeError, TypeError):
353
            limit = len(container_list) + 1
354
        for index, container in enumerate(container_list):
355
            if 'bytes' in container:
356
                size = format_size(container['bytes'])
357
            cname = '%s. %s' % (index + 1, bold(container['name']))
358
            if self.get_argument('detail'):
359
                print(cname)
360
                pretty_c = container.copy()
361
                if 'bytes' in container:
362
                    pretty_c['bytes'] = '%s (%s)' % (container['bytes'], size)
363
                print_dict(pretty_keys(pretty_c), exclude=('name'))
364
                print
365
            else:
366
                if 'count' in container and 'bytes' in container:
367
                    print('%s (%s, %s objects)'\
368
                    % (cname, size, container['count']))
369
                else:
370
                    print(cname)
371
            if limit <= index < len(container_list) and index % limit == 0:
372
                print('(press "enter" to continue)')
373
                sys.stdin.read(1)
374

    
375
    def main(self, container____path__=None):
376
        super(self.__class__, self).main(container____path__)
377
        try:
378
            if self.container is None:
379
                r = self.client.account_get(limit=self.get_argument('limit'),
380
                    marker=self.get_argument('marker'),
381
                    if_modified_since=self.get_argument('if_modified_since'),
382
                    if_unmodified_since=self.get_argument(\
383
                        'if_unmodified_since'),
384
                    until=self.get_argument('until'),
385
                    show_only_shared=self.get_argument('shared'))
386
                self.print_containers(r.json)
387
            else:
388
                r = self.client.container_get(limit=self.get_argument('limit'),
389
                    marker=self.get_argument('marker'),
390
                    prefix=self.get_argument('prefix'),
391
                    delimiter=self.get_argument('delimiter'),
392
                    path=self.get_argument('path'),
393
                    if_modified_since=self.get_argument('if_modified_since'),
394
                    if_unmodified_since=self.get_argument(\
395
                        'if_unmodified_since'),
396
                    until=self.get_argument('until'),
397
                    meta=self.get_argument('meta'),
398
                    show_only_shared=self.get_argument('shared'))
399
                self.print_objects(r.json)
400
        except ClientError as err:
401
            raiseCLIError(err)
402

    
403

    
404
@command(pithos_cmds)
405
class store_mkdir(_store_container_command):
406
    """Create a directory"""
407

    
408
    def main(self, container___directory):
409
        super(self.__class__,
410
            self).main(container___directory, path_is_optional=False)
411
        try:
412
            self.client.create_directory(self.path)
413
        except ClientError as err:
414
            raiseCLIError(err)
415

    
416

    
417
@command(pithos_cmds)
418
class store_create(_store_container_command):
419
    """Create a container or a directory object"""
420

    
421
    def __init__(self, arguments={}):
422
        super(self.__class__, self).__init__(arguments)
423
        self.arguments['versioning'] = \
424
            ValueArgument('set container versioning (auto/none)',
425
            '--versioning')
426
        self.arguments['quota'] =\
427
            IntArgument('set default container quota', '--quota')
428
        self.arguments['meta'] =\
429
            KeyValueArgument(
430
                'set container metadata (can be repeated)', '--meta')
431
            #  MetaArgument('set container metadata', '--meta')
432

    
433
    def main(self, container____directory__):
434
        super(self.__class__, self).main(container____directory__)
435
        try:
436
            if self.path is None:
437
                self.client.container_put(quota=self.get_argument('quota'),
438
                    versioning=self.get_argument('versioning'),
439
                    metadata=self.get_argument('meta'))
440
            else:
441
                self.client.create_directory(self.path)
442
        except ClientError as err:
443
            raiseCLIError(err)
444

    
445

    
446
@command(pithos_cmds)
447
class store_copy(_store_container_command):
448
    """Copy an object"""
449

    
450
    def __init__(self, arguments={}):
451
        super(self.__class__, self).__init__(arguments)
452
        self.arguments['source_version'] = ValueArgument(\
453
            'copy specific version', '--source-version')
454
        self.arguments['public'] = ValueArgument(\
455
            'make object publicly accessible', '--public')
456
        self.arguments['content_type'] = ValueArgument(\
457
            'change object\'s content type', '--content-type')
458
        self.arguments['delimiter'] = DelimiterArgument(self,
459
            parsed_name='--delimiter',
460
            help=u'copy objects prefixed as src_object + delimiter')
461
        self.arguments['recursive'] = FlagArgument(
462
            'mass copy with delimiter /', ('-r', '--recursive'))
463

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

    
482

    
483
@command(pithos_cmds)
484
class store_move(_store_container_command):
485
    """Copy an object"""
486

    
487
    def __init__(self, arguments={}):
488
        super(self.__class__, self).__init__(arguments)
489

    
490
        self.arguments['source_version'] = ValueArgument(\
491
            'copy specific version', '--source-version')
492
        self.arguments['public'] = FlagArgument(\
493
            'make object publicly accessible', '--public')
494
        self.arguments['content_type'] = ValueArgument(\
495
            'change object\'s content type', '--content-type')
496
        self.arguments['delimiter'] = DelimiterArgument(self,
497
            parsed_name='--delimiter',
498
            help='move objects prefixed as src_object + delimiter')
499
        self.arguments['recursive'] = FlagArgument(\
500
            'copy with delimiter /', ('-r', '--recursive'))
501

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

    
520

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

    
525
    def __init__(self, arguments={}):
526
        super(self.__class__, self).__init__(arguments)
527
        self.arguments['progress_bar'] = ProgressBarArgument(\
528
            'do not show progress bar', '--no-progress-bar', False)
529

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

    
549

    
550
@command(pithos_cmds)
551
class store_truncate(_store_container_command):
552
    """Truncate remote file up to a size"""
553

    
554
    def main(self, container___path, size=0):
555
        super(self.__class__,
556
            self).main(container___path, path_is_optional=False)
557
        try:
558
            self.client.truncate_object(self.path, size)
559
        except ClientError as err:
560
            raiseCLIError(err)
561

    
562

    
563
@command(pithos_cmds)
564
class store_overwrite(_store_container_command):
565
    """Overwrite part (from start to end) of a remote file"""
566

    
567
    def __init__(self, arguments={}):
568
        super(self.__class__, self).__init__(arguments)
569
        self.arguments['progress_bar'] = ProgressBarArgument(\
570
            'do not show progress bar', '--no-progress-bar', False)
571

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

    
593

    
594
@command(pithos_cmds)
595
class store_manifest(_store_container_command):
596
    """Create a remote file with uploaded parts by manifestation"""
597

    
598
    def __init__(self, arguments={}):
599
        super(self.__class__, self).__init__(arguments)
600
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
601
        self.arguments['content_encoding'] = ValueArgument(\
602
            'provide the object MIME content type', '--content-encoding')
603
        self.arguments['content_disposition'] = ValueArgument(\
604
            'provide the presentation style of the object',
605
            '--content-disposition')
606
        self.arguments['content_type'] = ValueArgument(\
607
            'create object with specific content type', '--content-type')
608
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
609
            help='define object sharing policy ' +\
610
            '( "read=user1,grp1,user2,... write=user1,grp2,..." )')
611
        self.arguments['public'] = FlagArgument(\
612
            'make object publicly accessible', '--public')
613

    
614
    def main(self, container___path):
615
        super(self.__class__,
616
            self).main(container___path, path_is_optional=False)
617
        try:
618
            self.client.create_object_by_manifestation(self.path,
619
                content_encoding=self.get_argument('content_encoding'),
620
                content_disposition=self.get_argument('content_disposition'),
621
                content_type=self.get_argument('content_type'),
622
                sharing=self.get_argument('sharing'),
623
                public=self.get_argument('public'))
624
        except ClientError as err:
625
            raiseCLIError(err)
626

    
627

    
628
@command(pithos_cmds)
629
class store_upload(_store_container_command):
630
    """Upload a file"""
631

    
632
    def __init__(self, arguments={}):
633
        super(self.__class__, self).__init__(arguments)
634
        self.arguments['use_hashes'] = FlagArgument(\
635
            'provide hashmap file instead of data', '--use-hashes')
636
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
637
        self.arguments['unchunked'] = FlagArgument(\
638
            'avoid chunked transfer mode', '--unchunked')
639
        self.arguments['content_encoding'] = ValueArgument(\
640
            'provide the object MIME content type', '--content-encoding')
641
        self.arguments['content_disposition'] = ValueArgument(\
642
            'provide the presentation style of the object',
643
            '--content-disposition')
644
        self.arguments['content_type'] = ValueArgument(\
645
            'create object with specific content type', '--content-type')
646
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
647
            help='define sharing object policy ' +\
648
            '( "read=user1,grp1,user2,... write=user1,grp2,...')
649
        self.arguments['public'] = FlagArgument(\
650
            'make object publicly accessible', '--public')
651
        self.arguments['poolsize'] = IntArgument(\
652
            'set pool size', '--with-pool-size')
653
        self.arguments['progress_bar'] = ProgressBarArgument(\
654
            'do not show progress bar', '--no-progress-bar', False)
655

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

    
696

    
697
@command(pithos_cmds)
698
class store_cat(_store_container_command):
699
    """Print a file to console"""
700

    
701
    def __init__(self, arguments={}):
702
        super(self.__class__, self).__init__(arguments)
703
        self.arguments['range'] =\
704
            RangeArgument('show range of data', '--range')
705
        self.arguments['if_match'] =\
706
            ValueArgument('show output if ETags match', '--if-match')
707
        self.arguments['if_none_match'] =\
708
            ValueArgument('show output if ETags match', '--if-none-match')
709
        self.arguments['if_modified_since'] =\
710
            DateArgument('show output modified since then',
711
            '--if-modified-since')
712
        self.arguments['if_unmodified_since'] =\
713
            DateArgument('show output unmodified since then',
714
            '--if-unmodified-since')
715
        self.arguments['object_version'] =\
716
            ValueArgument('get the specific version', '--object-version')
717

    
718
    def main(self, container___path):
719
        super(self.__class__,
720
            self).main(container___path, path_is_optional=False)
721
        try:
722
            self.client.download_object(self.path, stdout,
723
            range=self.get_argument('range'),
724
            version=self.get_argument('object_version'),
725
            if_match=self.get_argument('if_match'),
726
            if_none_match=self.get_argument('if_none_match'),
727
            if_modified_since=self.get_argument('if_modified_since'),
728
            if_unmodified_since=self.get_argument('if_unmodified_since'))
729
        except ClientError as err:
730
            raiseCLIError(err)
731

    
732

    
733
@command(pithos_cmds)
734
class store_download(_store_container_command):
735
    """Download a file"""
736

    
737
    def __init__(self, arguments={}):
738
        super(self.__class__, self).__init__(arguments)
739
        self.arguments['resume'] = FlagArgument(parsed_name='--resume',
740
            help='Resume a previous download instead of overwritting it')
741
        self.arguments['range'] = RangeArgument(\
742
            'show range of data', '--range')
743
        self.arguments['if_match'] = ValueArgument(\
744
            'show output if ETags match', '--if-match')
745
        self.arguments['if_none_match'] = ValueArgument(\
746
            'show output if ETags match', '--if-none-match')
747
        self.arguments['if_modified_since'] = DateArgument(\
748
            'show output modified since then', '--if-modified-since')
749
        self.arguments['if_unmodified_since'] = DateArgument(\
750
            'show output unmodified since then', '--if-unmodified-since')
751
        self.arguments['object_version'] = ValueArgument(\
752
            'get the specific version', '--object-version')
753
        self.arguments['poolsize'] = IntArgument(\
754
            'set pool size', '--with-pool-size')
755
        self.arguments['progress_bar'] = ProgressBarArgument(\
756
            'do not show progress bar', '--no-progress-bar', False)
757

    
758
    def main(self, container___path, local_path):
759
        super(self.__class__,
760
            self).main(container___path, path_is_optional=False)
761

    
762
        # setup output stream
763
        if local_path is None:
764
            out = stdout
765
        else:
766
            try:
767
                if self.get_argument('resume'):
768
                    out = open(local_path, 'rwb+')
769
                else:
770
                    out = open(local_path, 'wb+')
771
            except IOError as err:
772
                raiseCLIError(err, 'Cannot write to file %s' % local_path, 1)
773
        poolsize = self.get_argument('poolsize')
774
        if poolsize is not None:
775
            self.client.POOL_SIZE = int(poolsize)
776

    
777
        try:
778
            progress_bar = self.arguments['progress_bar']
779
            download_cb = progress_bar.get_generator('Downloading')
780
            self.client.download_object(self.path, out,
781
                download_cb=download_cb,
782
                range=self.get_argument('range'),
783
                version=self.get_argument('object_version'),
784
                if_match=self.get_argument('if_match'),
785
                resume=self.get_argument('resume'),
786
                if_none_match=self.get_argument('if_none_match'),
787
                if_modified_since=self.get_argument('if_modified_since'),
788
                if_unmodified_since=self.get_argument('if_unmodified_since'))
789
            progress_bar.finish()
790
        except ClientError as err:
791
            progress_bar.finish()
792
            raiseCLIError(err)
793
        except KeyboardInterrupt:
794
            from threading import enumerate as activethreads
795
            stdout.write('\nFinishing active threads ')
796
            for thread in activethreads():
797
                stdout.flush()
798
                try:
799
                    thread.join()
800
                    stdout.write('.')
801
                except RuntimeError:
802
                    continue
803
            progress_bar.finish()
804
            print('\ndownload canceled by user')
805
            if local_path is not None:
806
                print('to resume, re-run with --resume')
807
        except Exception as e:
808
            progress_bar.finish()
809
            raiseCLIError(e)
810
        print
811

    
812

    
813
@command(pithos_cmds)
814
class store_hashmap(_store_container_command):
815
    """Get the hashmap of an object"""
816

    
817
    def __init__(self, arguments={}):
818
        super(self.__class__, self).__init__(arguments)
819
        self.arguments['if_match'] =\
820
            ValueArgument('show output if ETags match', '--if-match')
821
        self.arguments['if_none_match'] =\
822
            ValueArgument('show output if ETags match', '--if-none-match')
823
        self.arguments['if_modified_since'] =\
824
            DateArgument('show output modified since then',
825
            '--if-modified-since')
826
        self.arguments['if_unmodified_since'] =\
827
            DateArgument('show output unmodified since then',
828
            '--if-unmodified-since')
829
        self.arguments['object_version'] =\
830
            ValueArgument('get the specific version', '--object-version')
831

    
832
    def main(self, container___path):
833
        super(self.__class__,
834
            self).main(container___path, path_is_optional=False)
835
        try:
836
            data = self.client.get_object_hashmap(self.path,
837
                version=self.get_argument('object_version'),
838
                if_match=self.get_argument('if_match'),
839
                if_none_match=self.get_argument('if_none_match'),
840
                if_modified_since=self.get_argument('if_modified_since'),
841
                if_unmodified_since=self.get_argument('if_unmodified_since'))
842
        except ClientError as err:
843
            raiseCLIError(err)
844
        print_dict(data)
845

    
846

    
847
@command(pithos_cmds)
848
class store_delete(_store_container_command):
849
    """Delete a container [or an object]"""
850

    
851
    def __init__(self, arguments={}):
852
        super(self.__class__, self).__init__(arguments)
853
        self.arguments['until'] = DateArgument(\
854
            'remove history until that date', '--until')
855
        self.arguments['recursive'] = FlagArgument(\
856
            'empty dir or container and delete (if dir)',
857
            ('-r', '--recursive'))
858
        self.arguments['delimiter'] = DelimiterArgument(self,
859
            parsed_name='--delimiter',
860
            help='delete objects prefixed with <object><delimiter>')
861

    
862
    def main(self, container____path__):
863
        super(self.__class__, self).main(container____path__)
864
        try:
865
            if self.path is None:
866
                self.client.del_container(until=self.get_argument('until'),
867
                    delimiter=self.get_argument('delimiter'))
868
            else:
869
                # self.client.delete_object(self.path)
870
                self.client.del_object(self.path,
871
                    until=self.get_argument('until'),
872
                    delimiter=self.get_argument('delimiter'))
873
        except ClientError as err:
874
            raiseCLIError(err)
875

    
876

    
877
@command(pithos_cmds)
878
class store_purge(_store_container_command):
879
    """Purge a container"""
880

    
881
    def main(self, container):
882
        super(self.__class__, self).main(container)
883
        try:
884
            self.client.purge_container()
885
        except ClientError as err:
886
            raiseCLIError(err)
887

    
888

    
889
@command(pithos_cmds)
890
class store_publish(_store_container_command):
891
    """Publish an object"""
892

    
893
    def main(self, container___path):
894
        super(self.__class__,
895
            self).main(container___path, path_is_optional=False)
896
        try:
897
            url = self.client.publish_object(self.path)
898
        except ClientError as err:
899
            raiseCLIError(err)
900
        print(url)
901

    
902

    
903
@command(pithos_cmds)
904
class store_unpublish(_store_container_command):
905
    """Unpublish an object"""
906

    
907
    def main(self, container___path):
908
        super(self.__class__,
909
            self).main(container___path, path_is_optional=False)
910
        try:
911
            self.client.unpublish_object(self.path)
912
        except ClientError as err:
913
            raiseCLIError(err)
914

    
915

    
916
@command(pithos_cmds)
917
class store_permissions(_store_container_command):
918
    """Get object read / write permissions """
919

    
920
    def main(self, container___path):
921
        super(self.__class__,
922
            self).main(container___path, path_is_optional=False)
923
        try:
924
            reply = self.client.get_object_sharing(self.path)
925
            print_dict(reply)
926
        except ClientError as err:
927
            raiseCLIError(err)
928

    
929

    
930
@command(pithos_cmds)
931
class store_setpermissions(_store_container_command):
932
    """Set sharing permissions """
933

    
934
    def format_permition_dict(self, permissions):
935
        read = False
936
        write = False
937
        for perms in permissions:
938
            splstr = perms.split('=')
939
            if 'read' == splstr[0]:
940
                read = [user_or_group.strip() \
941
                for user_or_group in splstr[1].split(',')]
942
            elif 'write' == splstr[0]:
943
                write = [user_or_group.strip() \
944
                for user_or_group in splstr[1].split(',')]
945
            else:
946
                read = False
947
                write = False
948
        if not read and not write:
949
            raiseCLIError(None,
950
            'Usage:\tread=<groups,users> write=<groups,users>')
951
        return (read, write)
952

    
953
    def main(self, container___path, *permissions):
954
        super(self.__class__,
955
            self).main(container___path, path_is_optional=False)
956
        (read, write) = self.format_permition_dict(permissions)
957
        try:
958
            self.client.set_object_sharing(self.path,
959
                read_permition=read, write_permition=write)
960
        except ClientError as err:
961
            raiseCLIError(err)
962

    
963

    
964
@command(pithos_cmds)
965
class store_delpermissions(_store_container_command):
966
    """Delete all sharing permissions"""
967

    
968
    def main(self, container___path):
969
        super(self.__class__,
970
            self).main(container___path, path_is_optional=False)
971
        try:
972
            self.client.del_object_sharing(self.path)
973
        except ClientError as err:
974
            raiseCLIError(err)
975

    
976

    
977
@command(pithos_cmds)
978
class store_info(_store_container_command):
979
    """Get information for account [, container [or object]]"""
980

    
981
    def __init__(self, arguments={}):
982
        super(self.__class__, self).__init__(arguments)
983
        self.arguments['object_version'] =\
984
            ValueArgument(parsed_name='--object-version',
985
            help='show specific version \ (applies only for objects)')
986

    
987
    def main(self, container____path__=None):
988
        super(self.__class__, self).main(container____path__)
989
        try:
990
            if self.container is None:
991
                reply = self.client.get_account_info()
992
            elif self.path is None:
993
                reply = self.client.get_container_info(self.container)
994
            else:
995
                reply = self.client.get_object_info(self.path,
996
                    version=self.get_argument('object_version'))
997
        except ClientError as err:
998
            raiseCLIError(err)
999
        print_dict(reply)
1000

    
1001

    
1002
@command(pithos_cmds)
1003
class store_meta(_store_container_command):
1004
    """Get custom meta-content for account [, container [or object]]"""
1005

    
1006
    def __init__(self, arguments={}):
1007
        super(self.__class__, self).__init__(arguments)
1008
        self.arguments['detail'] =\
1009
            FlagArgument('show detailed output', '-l')
1010
        self.arguments['until'] =\
1011
            DateArgument('show metadata until then', '--until')
1012
        self.arguments['object_version'] =\
1013
            ValueArgument(parsed_name='--object-version',
1014
            help='show specific version \ (applies only for objects)')
1015

    
1016
    def main(self, container____path__=None):
1017
        super(self.__class__, self).main(container____path__)
1018

    
1019
        detail = self.get_argument('detail')
1020
        try:
1021
            until = self.get_argument('until')
1022
            if self.container is None:
1023
                print(bold(self.client.account))
1024
                if detail:
1025
                    reply = self.client.get_account_info(until=until)
1026
                else:
1027
                    reply = self.client.get_account_meta(until=until)
1028
                    reply = pretty_keys(reply, '-')
1029
            elif self.path is None:
1030
                print(bold('%s: %s' % (self.client.account, self.container)))
1031
                if detail:
1032
                    reply = self.client.get_container_info(until=until)
1033
                else:
1034
                    cmeta = self.client.get_container_meta(until=until)
1035
                    ometa = self.client.get_container_object_meta(until=until)
1036
                    reply = {'container-meta': pretty_keys(cmeta, '-'),
1037
                        'object-meta': pretty_keys(ometa, '-')}
1038
            else:
1039
                print(bold('%s: %s:%s'\
1040
                    % (self.client.account, self.container, self.path)))
1041
                version = self.get_argument('object_version')
1042
                if detail:
1043
                    reply = self.client.get_object_info(self.path,
1044
                        version=version)
1045
                else:
1046
                    reply = self.client.get_object_meta(self.path,
1047
                        version=version)
1048
                    reply = pretty_keys(pretty_keys(reply, '-'))
1049
        except ClientError as err:
1050
            raiseCLIError(err)
1051
        print_dict(reply)
1052

    
1053

    
1054
@command(pithos_cmds)
1055
class store_setmeta(_store_container_command):
1056
    """Set a new metadatum for account [, container [or object]]"""
1057

    
1058
    def main(self, metakey___metaval, container____path__=None):
1059
        super(self.__class__, self).main(container____path__)
1060
        try:
1061
            metakey, metavalue = metakey___metaval.split(':')
1062
        except ValueError as err:
1063
            raiseCLIError(err, 'Usage:  metakey:metavalue', importance=1)
1064
        try:
1065
            if self.container is None:
1066
                self.client.set_account_meta({metakey: metavalue})
1067
            elif self.path is None:
1068
                self.client.set_container_meta({metakey: metavalue})
1069
            else:
1070
                self.client.set_object_meta(self.path, {metakey: metavalue})
1071
        except ClientError as err:
1072
            raiseCLIError(err)
1073

    
1074

    
1075
@command(pithos_cmds)
1076
class store_delmeta(_store_container_command):
1077
    """Delete an existing metadatum of account [, container [or object]]"""
1078

    
1079
    def main(self, metakey, container____path__=None):
1080
        super(self.__class__, self).main(container____path__)
1081
        try:
1082
            if self.container is None:
1083
                self.client.del_account_meta(metakey)
1084
            elif self.path is None:
1085
                self.client.del_container_meta(metakey)
1086
            else:
1087
                self.client.del_object_meta(self.path, metakey)
1088
        except ClientError as err:
1089
            raiseCLIError(err)
1090

    
1091

    
1092
@command(pithos_cmds)
1093
class store_quota(_store_account_command):
1094
    """Get  quota for account [or container]"""
1095

    
1096
    def main(self, container=None):
1097
        super(self.__class__, self).main()
1098
        try:
1099
            if container is None:
1100
                reply = self.client.get_account_quota()
1101
            else:
1102
                reply = self.client.get_container_quota(container)
1103
        except ClientError as err:
1104
            raiseCLIError(err)
1105
        print_dict(reply)
1106

    
1107

    
1108
@command(pithos_cmds)
1109
class store_setquota(_store_account_command):
1110
    """Set new quota (in KB) for account [or container]"""
1111

    
1112
    def main(self, quota, container=None):
1113
        super(self.__class__, self).main()
1114
        try:
1115
            if container is None:
1116
                self.client.set_account_quota(quota)
1117
            else:
1118
                self.client.container = container
1119
                self.client.set_container_quota(quota)
1120
        except ClientError as err:
1121
            raiseCLIError(err)
1122

    
1123

    
1124
@command(pithos_cmds)
1125
class store_versioning(_store_account_command):
1126
    """Get  versioning for account [or container ]"""
1127

    
1128
    def main(self, container=None):
1129
        super(self.__class__, self).main()
1130
        try:
1131
            if container is None:
1132
                reply = self.client.get_account_versioning()
1133
            else:
1134
                reply = self.client.get_container_versioning(container)
1135
        except ClientError as err:
1136
            raiseCLIError(err)
1137
        print_dict(reply)
1138

    
1139

    
1140
@command(pithos_cmds)
1141
class store_setversioning(_store_account_command):
1142
    """Set new versioning (auto, none) for account [or container]"""
1143

    
1144
    def main(self, versioning, container=None):
1145
        super(self.__class__, self).main()
1146
        try:
1147
            if container is None:
1148
                self.client.set_account_versioning(versioning)
1149
            else:
1150
                self.client.container = container
1151
                self.client.set_container_versioning(versioning)
1152
        except ClientError as err:
1153
            raiseCLIError(err)
1154

    
1155

    
1156
@command(pithos_cmds)
1157
class store_group(_store_account_command):
1158
    """Get user groups details for account"""
1159

    
1160
    def main(self):
1161
        super(self.__class__, self).main()
1162
        try:
1163
            reply = self.client.get_account_group()
1164
        except ClientError as err:
1165
            raiseCLIError(err)
1166
        print_dict(reply)
1167

    
1168

    
1169
@command(pithos_cmds)
1170
class store_setgroup(_store_account_command):
1171
    """Create/update a new user group on account"""
1172

    
1173
    def main(self, groupname, *users):
1174
        super(self.__class__, self).main()
1175
        try:
1176
            self.client.set_account_group(groupname, users)
1177
        except ClientError as err:
1178
            raiseCLIError(err)
1179

    
1180

    
1181
@command(pithos_cmds)
1182
class store_delgroup(_store_account_command):
1183
    """Delete a user group on an account"""
1184

    
1185
    def main(self, groupname):
1186
        super(self.__class__, self).main()
1187
        try:
1188
            self.client.del_account_group(groupname)
1189
        except ClientError as err:
1190
            raiseCLIError(err)
1191

    
1192

    
1193
@command(pithos_cmds)
1194
class store_sharers(_store_account_command):
1195
    """List the accounts that share objects with default account"""
1196

    
1197
    def __init__(self, arguments={}):
1198
        super(self.__class__, self).__init__(arguments)
1199
        self.arguments['detail'] =\
1200
            FlagArgument('show detailed output', '-l')
1201
        self.arguments['limit'] =\
1202
            IntArgument('show limited output', '--n', default=1000)
1203
        self.arguments['marker'] =\
1204
            ValueArgument('show output greater then marker', '--marker')
1205

    
1206
    def main(self):
1207
        super(self.__class__, self).main()
1208
        try:
1209
            marker = self.get_argument('marker')
1210
            accounts = self.client.get_sharing_accounts(marker=marker)
1211
        except ClientError as err:
1212
            raiseCLIError(err)
1213

    
1214
        for acc in accounts:
1215
            stdout.write(bold(acc['name']) + ' ')
1216
            if self.get_argument('detail'):
1217
                print_dict(acc, exclude='name', ident=4)
1218
        if not self.get_argument('detail'):
1219
            print
1220

    
1221

    
1222
@command(pithos_cmds)
1223
class store_versions(_store_container_command):
1224
    """Get the version list of an object"""
1225

    
1226
    def main(self, container___path):
1227
        super(store_versions, self).main(container___path)
1228
        try:
1229
            versions = self.client.get_object_versionlist(self.path)
1230
        except ClientError as err:
1231
            raiseCLIError(err)
1232

    
1233
        print('%s:%s versions' % (self.container, self.path))
1234
        for vitem in versions:
1235
            t = localtime(float(vitem[1]))
1236
            vid = bold(unicode(vitem[0]))
1237
            print('\t%s \t(%s)' % (vid, strftime('%d-%m-%Y %H:%M:%S', t)))