Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 3dabe5d2

History | View | Annotate | Download (45.1 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.errors import CLIError, raiseCLIError
36
from kamaki.cli.utils import format_size, print_dict, pretty_keys
37
from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
38
from kamaki.cli.commands import _command_init
39
from kamaki.clients.pithos import PithosClient, ClientError
40
from kamaki.cli.utils import bold
41
from sys import stdout
42
from time import localtime, strftime
43
from datetime import datetime as dtm
44

    
45

    
46
API_DESCRIPTION = dict(store='Pithos+ storage commands')
47

    
48
try:
49
    from progress.bar import IncrementalBar
50
except ImportError:
51
    # progress not installed - pls, pip install progress
52
    pass
53

    
54
# Argument functionality
55

    
56

    
57
class DelimiterArgument(ValueArgument):
58
    def __init__(self, caller_obj, help='', parsed_name=None, default=None):
59
        super(DelimiterArgument, self).__init__(help, parsed_name, default)
60
        self.caller_obj = caller_obj
61

    
62
    @property
63
    def value(self):
64
        if self.caller_obj.get_argument('recursive'):
65
            return '/'
66
        return getattr(self, '_value', self.default)
67

    
68
    @value.setter
69
    def value(self, newvalue):
70
        self._value = newvalue
71

    
72

    
73
class MetaArgument(ValueArgument):
74
    @property
75
    def value(self):
76
        if self._value is None:
77
            return self.default
78
        metadict = dict()
79
        for metastr in self._value.split('_'):
80
            (key, val) = metastr.split(':')
81
            metadict[key] = val
82
        return metadict
83

    
84
    @value.setter
85
    def value(self, newvalue):
86
        if newvalue is None:
87
            self._value = self.default
88
        self._value = newvalue
89

    
90

    
91
class ProgressBarArgument(FlagArgument):
92

    
93
    def __init__(self, help='', parsed_name='', default=True):
94
        self.suffix = '%(percent)d%%'
95
        super(ProgressBarArgument, self).__init__(help, parsed_name, default)
96
        try:
97
            self.bar = IncrementalBar()
98
        except NameError:
99
            print('Waring: no progress bar functionality')
100

    
101
    @property
102
    def value(self):
103
        return getattr(self, '_value', self.default)
104

    
105
    @value.setter
106
    def value(self, newvalue):
107
        """By default, it is on (True)"""
108
        self._value = not newvalue
109

    
110
    def get_generator(self, message, message_len=25):
111
        try:
112
            bar = ProgressBar(message.ljust(message_len))
113
        except NameError:
114
            return None
115
        return bar.get_generator()
116

    
117

    
118
try:
119
    class ProgressBar(IncrementalBar):
120
        suffix = '%(percent)d%% - %(eta)ds'
121

    
122
        def get_generator(self):
123
            def progress_gen(n):
124
                for i in self.iter(range(n)):
125
                    yield
126
                yield
127
            return progress_gen
128
except NameError:
129
    pass
130

    
131

    
132
class SharingArgument(ValueArgument):
133
    @property
134
    def value(self):
135
        return getattr(self, '_value', self.default)
136

    
137
    @value.setter
138
    def value(self, newvalue):
139
        perms = {}
140
        try:
141
            permlist = newvalue.split(' ')
142
        except AttributeError:
143
            return
144
        for p in permlist:
145
            try:
146
                (key, val) = p.split('=')
147
            except ValueError:
148
                raise CLIError(message='Error in --sharing',
149
                    details='Incorrect format',
150
                    importance=1)
151
            if key.lower() not in ('read', 'write'):
152
                raise CLIError(message='Error in --sharing',
153
                    details='Invalid permition key %s' % key,
154
                    importance=1)
155
            val_list = val.split(',')
156
            if not key in perms:
157
                perms[key] = []
158
            for item in val_list:
159
                if item not in perms[key]:
160
                    perms[key].append(item)
161
        self._value = perms
162

    
163

    
164
class RangeArgument(ValueArgument):
165
    @property
166
    def value(self):
167
        return getattr(self, '_value', self.default)
168

    
169
    @value.setter
170
    def value(self, newvalue):
171
        if newvalue is None:
172
            self._value = self.default
173
            return
174
        (start, end) = newvalue.split('-')
175
        (start, end) = (int(start), int(end))
176
        self._value = '%s-%s' % (start, end)
177

    
178

    
179
class DateArgument(ValueArgument):
180
    DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
181
        "%A, %d-%b-%y %H:%M:%S GMT",
182
        "%a, %d %b %Y %H:%M:%S GMT"]
183

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

    
186
    @property
187
    def value(self):
188
        return getattr(self, '_value', self.default)
189

    
190
    @value.setter
191
    def value(self, newvalue):
192
        if newvalue is None:
193
            return
194
        self._value = self.format_date(newvalue)
195

    
196
    def format_date(self, datestr):
197
        for format in self.INPUT_FORMATS:
198
            try:
199
                t = dtm.strptime(datestr, format)
200
            except ValueError:
201
                continue
202
            self._value = t.strftime(self.DATE_FORMATS[0])
203
            return
204
        raise CLIError('Date Argument Error',
205
            details='%s not a valid date. correct formats:\n\t%s'\
206
            % (datestr, self.INPUT_FORMATS))
207

    
208

    
209
# Command specs
210

    
211

    
212
class _pithos_init(_command_init):
213
    def main(self):
214
        self.token = self.config.get('store', 'token')\
215
            or self.config.get('global', 'token')
216
        self.base_url = self.config.get('store', 'url')\
217
            or self.config.get('global', 'url')
218
        self.account = self.config.get('store', 'account')\
219
            or self.config.get('global', 'account')
220
        self.container = self.config.get('store', 'container')\
221
            or self.config.get('global', 'container')
222
        self.client = PithosClient(base_url=self.base_url,
223
            token=self.token,
224
            account=self.account,
225
            container=self.container)
226

    
227

    
228
class _store_account_command(_pithos_init):
229
    """Base class for account level storage commands"""
230

    
231
    def __init__(self, arguments={}):
232
        super(_store_account_command, self).__init__(arguments)
233
        self.arguments['account'] =\
234
            ValueArgument('Specify the account', '--account')
235

    
236
    def generator(self, message):
237
        return None
238

    
239
    def main(self):
240
        super(_store_account_command, self).main()
241
        if self.arguments['account'].value is not None:
242
            self.client.account = self.arguments['account'].value
243

    
244

    
245
class _store_container_command(_store_account_command):
246
    """Base class for container level storage commands"""
247

    
248
    def __init__(self, arguments={}):
249
        super(_store_container_command, self).__init__(arguments)
250
        self.arguments['container'] =\
251
            ValueArgument('Specify the container name', '--container')
252
        self.container = None
253
        self.path = None
254

    
255
    def extract_container_and_path(self,
256
        container_with_path,
257
        path_is_optional=True):
258
        assert isinstance(container_with_path, str)
259
        if ':' not in container_with_path:
260
            if self.get_argument('container') is not None:
261
                self.container = self.get_argument('container')
262
            else:
263
                self.container = self.client.container
264
            if self.container is None:
265
                self.container = container_with_path
266
            else:
267
                self.path = container_with_path
268
            if not path_is_optional and self.path is None:
269
                raise CLIError(message="Object path is missing", status=11)
270
            return
271
        cnp = container_with_path.split(':')
272
        self.container = cnp[0]
273
        try:
274
            self.path = cnp[1]
275
        except IndexError:
276
            if path_is_optional:
277
                self.path = None
278
            else:
279
                raise CLIError(message="Object path is missing", status=11)
280

    
281
    def main(self, container_with_path=None, path_is_optional=True):
282
        super(_store_container_command, self).main()
283
        if container_with_path is not None:
284
            self.extract_container_and_path(container_with_path,
285
                path_is_optional)
286
            self.client.container = self.container
287
        elif self.get_argument('container') is not None:
288
            self.client.container = self.get_argument('container')
289
        self.container = self.client.container
290

    
291

    
292
@command()
293
class store_list(_store_container_command):
294
    """List containers, object trees or objects in a directory
295
    """
296

    
297
    def __init__(self, arguments={}):
298
        super(store_list, self).__init__(arguments)
299
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
300
        self.arguments['show_size'] =\
301
            ValueArgument('print output in chunks of size N', '-N')
302
        self.arguments['limit'] = IntArgument('show limited output', '-n')
303
        self.arguments['marker'] =\
304
            ValueArgument('show output greater that marker', '--marker')
305
        self.arguments['prefix'] =\
306
            ValueArgument('show output staritng with prefix', '--prefix')
307
        self.arguments['delimiter'] =\
308
            ValueArgument('show output up to delimiter', '--delimiter')
309
        self.arguments['path'] =\
310
            ValueArgument('show output starting with prefix up to /', '--path')
311
        self.arguments['meta'] =\
312
            ValueArgument('show output haviung the specified meta keys',
313
            '--meta', default=[])
314
        self.arguments['if_modified_since'] =\
315
            ValueArgument('show output modified since then',
316
                '--if-modified-since')
317
        self.arguments['if_unmodified_since'] =\
318
            ValueArgument('show output not modified since then',
319
            '--if-unmodified-since')
320
        self.arguments['until'] =\
321
            DateArgument('show metadata until then', '--until')
322
        self.arguments['format'] =\
323
            ValueArgument('format to parse until data (default: d/m/Y H:M:S',
324
            '--format')
325
        self.arguments['shared'] = FlagArgument('show only shared', '--shared')
326
        self.arguments['public'] = FlagArgument('show only public', '--public')
327

    
328
    def print_objects(self, object_list):
329
        import sys
330
        try:
331
            limit = self.get_argument('show_size')
332
            limit = int(limit)
333
        except (AttributeError, TypeError):
334
            limit = len(object_list) + 1
335
        #index = 0
336
        for index, obj in enumerate(object_list):
337
            if 'content_type' not in obj:
338
                continue
339
            pretty_obj = obj.copy()
340
            index += 1
341
            empty_space = ' ' * (len(str(len(object_list))) - len(str(index)))
342
            if obj['content_type'] == 'application/directory':
343
                isDir = True
344
                size = 'D'
345
            else:
346
                isDir = False
347
                size = format_size(obj['bytes'])
348
                pretty_obj['bytes'] = '%s (%s)' % (obj['bytes'], size)
349
            oname = bold(obj['name'])
350
            if self.get_argument('detail'):
351
                print('%s%s. %s' % (empty_space, index, oname))
352
                print_dict(pretty_keys(pretty_obj), exclude=('name'))
353
                print
354
            else:
355
                oname = '%s%s. %6s %s' % (empty_space, index, size, oname)
356
                oname += '/' if isDir else ''
357
                print(oname)
358
            if limit <= index < len(object_list) and index % limit == 0:
359
                print('(press "enter" to continue)')
360
                sys.stdin.read(1)
361

    
362
    def print_containers(self, container_list):
363
        import sys
364
        try:
365
            limit = self.get_argument('show_size')
366
            limit = int(limit)
367
        except (AttributeError, TypeError):
368
            limit = len(container_list) + 1
369
        for index, container in enumerate(container_list):
370
            if 'bytes' in container:
371
                size = format_size(container['bytes'])
372
            cname = '%s. %s' % (index + 1, bold(container['name']))
373
            if self.get_argument('detail'):
374
                print(cname)
375
                pretty_c = container.copy()
376
                if 'bytes' in container:
377
                    pretty_c['bytes'] = '%s (%s)' % (container['bytes'], size)
378
                print_dict(pretty_keys(pretty_c), exclude=('name'))
379
                print
380
            else:
381
                if 'count' in container and 'bytes' in container:
382
                    print('%s (%s, %s objects)'\
383
                    % (cname, size, container['count']))
384
                else:
385
                    print(cname)
386
            if limit <= index < len(container_list) and index % limit == 0:
387
                print('(press "enter" to continue)')
388
                sys.stdin.read(1)
389

    
390
    def main(self, container____path__=None):
391
        super(self.__class__, self).main(container____path__)
392
        try:
393
            if self.container is None:
394
                r = self.client.account_get(limit=self.get_argument('limit'),
395
                    marker=self.get_argument('marker'),
396
                    if_modified_since=self.get_argument('if_modified_since'),
397
                    if_unmodified_since=self.get_argument(\
398
                        'if_unmodified_since'),
399
                    until=self.get_argument('until'),
400
                    show_only_shared=self.get_argument('shared'))
401
                self.print_containers(r.json)
402
            else:
403
                r = self.client.container_get(limit=self.get_argument('limit'),
404
                    marker=self.get_argument('marker'),
405
                    prefix=self.get_argument('prefix'),
406
                    delimiter=self.get_argument('delimiter'),
407
                    path=self.get_argument('path'),
408
                    if_modified_since=self.get_argument('if_modified_since'),
409
                    if_unmodified_since=self.get_argument(\
410
                        'if_unmodified_since'),
411
                    until=self.get_argument('until'),
412
                    meta=self.get_argument('meta'),
413
                    show_only_shared=self.get_argument('shared'))
414
                self.print_objects(r.json)
415
        except ClientError as err:
416
            raiseCLIError(err)
417

    
418

    
419
@command()
420
class store_mkdir(_store_container_command):
421
    """Create a directory"""
422

    
423
    def main(self, container___directory):
424
        super(self.__class__,
425
            self).main(container___directory, path_is_optional=False)
426
        try:
427
            self.client.create_directory(self.path)
428
        except ClientError as err:
429
            raiseCLIError(err)
430

    
431

    
432
@command()
433
class store_create(_store_container_command):
434
    """Create a container or a directory object"""
435

    
436
    def __init__(self, arguments={}):
437
        super(self.__class__, self).__init__(arguments)
438
        self.arguments['versioning'] = \
439
            ValueArgument('set container versioning (auto/none)',
440
            '--versioning')
441
        self.arguments['quota'] =\
442
            IntArgument('set default container quota', '--quota')
443
        self.arguments['meta'] =\
444
            MetaArgument('set container metadata', '--meta')
445

    
446
    def main(self, container____directory__):
447
        super(self.__class__, self).main(container____directory__)
448
        try:
449
            if self.path is None:
450
                self.client.container_put(quota=self.get_argument('quota'),
451
                    versioning=self.get_argument('versioning'),
452
                    metadata=self.get_argument('meta'))
453
            else:
454
                self.client.create_directory(self.path)
455
        except ClientError as err:
456
            raiseCLIError(err)
457

    
458

    
459
@command()
460
class store_copy(_store_container_command):
461
    """Copy an object"""
462

    
463
    def __init__(self, arguments={}):
464
        super(self.__class__, self).__init__(arguments)
465
        self.arguments['source_version'] = ValueArgument(\
466
            'copy specific version', '--source-version')
467
        self.arguments['public'] = ValueArgument(\
468
            'make object publicly accessible', '--public')
469
        self.arguments['content_type'] = ValueArgument(\
470
            'change object\'s content type', '--content-type')
471
        self.arguments['delimiter'] = DelimiterArgument(self,
472
            parsed_name='--delimiter',
473
            help=u'copy objects prefixed as src_object + delimiter')
474
        self.arguments['recursive'] = FlagArgument(
475
            'mass copy with delimiter /', ('-r', '--recursive'))
476

    
477
    def main(self, source_container___path, destination_container____path__):
478
        super(self.__class__,
479
            self).main(source_container___path, path_is_optional=False)
480
        try:
481
            dst = destination_container____path__.split(':')
482
            dst_cont = dst[0]
483
            dst_path = dst[1] if len(dst) > 1 else False
484
            self.client.copy_object(src_container=self.container,
485
                src_object=self.path,
486
                dst_container=dst_cont,
487
                dst_object=dst_path,
488
                source_version=self.get_argument('source_version'),
489
                public=self.get_argument('public'),
490
                content_type=self.get_argument('content_type'),
491
                delimiter=self.get_argument('delimiter'))
492
        except ClientError as err:
493
            raiseCLIError(err)
494

    
495

    
496
@command()
497
class store_move(_store_container_command):
498
    """Copy an object"""
499

    
500
    def __init__(self, arguments={}):
501
        super(self.__class__, self).__init__(arguments)
502

    
503
        self.arguments['source_version'] = ValueArgument(\
504
            'copy specific version', '--source-version')
505
        self.arguments['public'] = FlagArgument(\
506
            'make object publicly accessible', '--public')
507
        self.arguments['content_type'] = ValueArgument(\
508
            'change object\'s content type', '--content-type')
509
        self.arguments['delimiter'] = DelimiterArgument(self,
510
            parsed_name='--delimiter',
511
            help='move objects prefixed as src_object + delimiter')
512
        self.arguments['recursive'] = FlagArgument(\
513
            'copy with delimiter /', ('-r', '--recursive'))
514

    
515
    def main(self, source_container___path, destination_container____path__):
516
        super(self.__class__,
517
            self).main(source_container___path, path_is_optional=False)
518
        try:
519
            dst = destination_container____path__.split(':')
520
            dst_cont = dst[0]
521
            dst_path = dst[1] if len(dst) > 1 else False
522
            self.client.move_object(src_container=self.container,
523
                src_object=self.path,
524
                dst_container=dst_cont,
525
                dst_object=dst_path,
526
                source_version=self.get_argument('source_version'),
527
                public=self.get_argument('public'),
528
                content_type=self.get_argument('content_type'),
529
                delimiter=self.get_argument('delimiter'))
530
        except ClientError as err:
531
            raiseCLIError(err)
532

    
533

    
534
@command()
535
class store_append(_store_container_command):
536
    """Append local file to (existing) remote object"""
537

    
538
    def __init__(self, arguments={}):
539
        super(self.__class__, self).__init__(arguments)
540
        self.arguments['progress_bar'] = ProgressBarArgument(\
541
            'do not show progress bar', '--no-progress-bar')
542

    
543
    def main(self, local_path, container___path):
544
        super(self.__class__,
545
            self).main(container___path, path_is_optional=False)
546
        try:
547
            f = open(local_path, 'r')
548
            try:
549
                progress_bar = self.arguments['progress_bar']
550
                upload_cb = progress_bar.get_generator('Appending blocks')
551
            except Exception:
552
                upload_cb = None
553
            self.client.append_object(object=self.path,
554
                source_file=f,
555
                upload_cb=upload_cb)
556
        except ClientError as err:
557
            raiseCLIError(err)
558

    
559

    
560
@command()
561
class store_truncate(_store_container_command):
562
    """Truncate remote file up to a size"""
563

    
564
    def main(self, container___path, size=0):
565
        super(self.__class__,
566
            self).main(container___path, path_is_optional=False)
567
        try:
568
            self.client.truncate_object(self.path, size)
569
        except ClientError as err:
570
            raiseCLIError(err)
571

    
572

    
573
@command()
574
class store_overwrite(_store_container_command):
575
    """Overwrite part (from start to end) of a remote file"""
576

    
577
    def __init__(self, arguments={}):
578
        super(self.__class__, self).__init__(arguments)
579
        self.arguments['progress_bar'] = ProgressBarArgument(\
580
            'do not show progress bar', '--no-progress-bar')
581

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

    
600

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

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

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

    
634

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

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

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

    
698

    
699
@command()
700
class store_cat(_store_container_command):
701
    """Print a file to console"""
702

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

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

    
731

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

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

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

    
761
        # setup output stream
762
        if local_path is None:
763
            out = stdout
764
        else:
765
            try:
766
                if self.get_argument('resume'):
767
                    out = open(local_path, 'rwb+')
768
                else:
769
                    out = open(local_path, 'wb+')
770
            except IOError as err:
771
                raise CLIError(message='Cannot write to file %s - %s'\
772
                    % (local_path, unicode(err)),
773
                    importance=1)
774
        progress_bar = self.arguments['progress_bar']
775
        download_cb = progress_bar.get_generator('Downloading')
776
        poolsize = self.get_argument('poolsize')
777
        if poolsize is not None:
778
            self.POOL_SIZE = int(poolsize)
779

    
780
        try:
781
            self.client.download_object(self.path, out,
782
                download_cb=download_cb,
783
                range=self.get_argument('range'),
784
                version=self.get_argument('object_version'),
785
                if_match=self.get_argument('if_match'),
786
                resume=self.get_argument('resume'),
787
                if_none_match=self.get_argument('if_none_match'),
788
                if_modified_since=self.get_argument('if_modified_since'),
789
                if_unmodified_since=self.get_argument('if_unmodified_since'))
790
        except ClientError as err:
791
            raiseCLIError(err)
792
        except KeyboardInterrupt:
793
            print('\ndownload canceled by user')
794
            if local_path is not None:
795
                print('to resume, re-run with --resume')
796
        print
797

    
798

    
799
@command()
800
class store_hashmap(_store_container_command):
801
    """Get the hashmap of an object"""
802

    
803
    def __init__(self, arguments={}):
804
        super(self.__class__, self).__init__(arguments)
805
        self.arguments['if_match'] =\
806
            ValueArgument('show output if ETags match', '--if-match')
807
        self.arguments['if_none_match'] =\
808
            ValueArgument('show output if ETags match', '--if-none-match')
809
        self.arguments['if_modified_since'] =\
810
            DateArgument('show output modified since then',
811
            '--if-modified-since')
812
        self.arguments['if_unmodified_since'] =\
813
            DateArgument('show output unmodified since then',
814
            '--if-unmodified-since')
815
        self.arguments['object_version'] =\
816
            ValueArgument('get the specific version', '--object-version')
817

    
818
    def main(self, container___path):
819
        super(self.__class__,
820
            self).main(container___path, path_is_optional=False)
821
        try:
822
            data = self.client.get_object_hashmap(self.path,
823
                version=self.arguments('object_version'),
824
                if_match=self.arguments('if_match'),
825
                if_none_match=self.arguments('if_none_match'),
826
                if_modified_since=self.arguments('if_modified_since'),
827
                if_unmodified_since=self.arguments('if_unmodified_since'))
828
        except ClientError as err:
829
            raiseCLIError(err)
830
        print_dict(data)
831

    
832

    
833
@command()
834
class store_delete(_store_container_command):
835
    """Delete a container [or an object]"""
836

    
837
    def __init__(self, arguments={}):
838
        super(self.__class__, self).__init__(arguments)
839
        self.arguments['until'] = DateArgument(\
840
            'remove history until that date', '--until')
841
        self.arguments['recursive'] = FlagArgument(\
842
            'empty dir or container and delete (if dir)',
843
            ('-r', '--recursive'))
844
        self.arguments['delimiter'] = DelimiterArgument(self,
845
            parsed_name='--delimiter',
846
            help='delete objects prefixed with <object><delimiter>')
847

    
848
    def main(self, container____path__):
849
        super(self.__class__, self).main(container____path__)
850
        try:
851
            if self.path is None:
852
                self.client.del_container(until=self.get_argument('until'),
853
                    delimiter=self.get_argument('delimiter'))
854
            else:
855
                # self.client.delete_object(self.path)
856
                self.client.del_object(self.path,
857
                    until=self.get_argument('until'),
858
                    delimiter=self.get_argument('delimiter'))
859
        except ClientError as err:
860
            raiseCLIError(err)
861

    
862

    
863
@command()
864
class store_purge(_store_container_command):
865
    """Purge a container"""
866

    
867
    def main(self, container):
868
        super(self.__class__, self).main(container)
869
        try:
870
            self.client.purge_container()
871
        except ClientError as err:
872
            raiseCLIError(err)
873

    
874

    
875
@command()
876
class store_publish(_store_container_command):
877
    """Publish an object"""
878

    
879
    def main(self, container___path):
880
        super(self.__class__,
881
            self).main(container___path, path_is_optional=False)
882
        try:
883
            self.client.publish_object(self.path)
884
        except ClientError as err:
885
            raiseCLIError(err)
886

    
887

    
888
@command()
889
class store_unpublish(_store_container_command):
890
    """Unpublish an object"""
891

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

    
900

    
901
@command()
902
class store_permissions(_store_container_command):
903
    """Get object read/write permissions """
904

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

    
914

    
915
@command()
916
class store_setpermissions(_store_container_command):
917
    """Set sharing permissions """
918

    
919
    def format_permition_dict(self, permissions):
920
        read = False
921
        write = False
922
        for perms in permissions:
923
            splstr = perms.split('=')
924
            if 'read' == splstr[0]:
925
                read = [user_or_group.strip() \
926
                for user_or_group in splstr[1].split(',')]
927
            elif 'write' == splstr[0]:
928
                write = [user_or_group.strip() \
929
                for user_or_group in splstr[1].split(',')]
930
            else:
931
                read = False
932
                write = False
933
        if not read and not write:
934
            raise CLIError(importance=0,
935
                message='Usage:\tread=<groups,users> write=<groups,users>')
936
        return (read, write)
937

    
938
    def main(self, container___path, *permissions):
939
        super(self.__class__,
940
            self).main(container___path, path_is_optional=False)
941
        (read, write) = self.format_permition_dict(permissions)
942
        try:
943
            self.client.set_object_sharing(self.path,
944
                read_permition=read, write_permition=write)
945
        except ClientError as err:
946
            raiseCLIError(err)
947

    
948

    
949
@command()
950
class store_delpermissions(_store_container_command):
951
    """Delete all sharing permissions"""
952

    
953
    def main(self, container___path):
954
        super(self.__class__,
955
            self).main(container___path, path_is_optional=False)
956
        try:
957
            self.client.del_object_sharing(self.path)
958
        except ClientError as err:
959
            raiseCLIError(err)
960

    
961

    
962
@command()
963
class store_info(_store_container_command):
964
    """Get information for account [, container [or object]]"""
965

    
966
    def main(self, container____path__=None):
967
        super(self.__class__, self).main(container____path__)
968
        try:
969
            if self.container is None:
970
                reply = self.client.get_account_info()
971
            elif self.path is None:
972
                reply = self.client.get_container_info(self.container)
973
            else:
974
                reply = self.client.get_object_info(self.path)
975
        except ClientError as err:
976
            raiseCLIError(err)
977
        print_dict(reply)
978

    
979

    
980
@command()
981
class store_meta(_store_container_command):
982
    """Get custom meta-content for account [, container [or object]]"""
983

    
984
    def __init__(self, arguments={}):
985
        super(self.__class__, self).__init__(arguments)
986
        self.arguments['detail'] =\
987
            FlagArgument('show detailed output', '-l')
988
        self.arguments['until'] =\
989
            DateArgument('show metadata until then', '--until')
990
        self.arguments['object_version'] =\
991
            ValueArgument(parsed_name='--object-version',
992
            help='show specific version \ (applies only for objects)')
993

    
994
    def main(self, container____path__=None):
995
        super(self.__class__, self).main(container____path__)
996

    
997
        detail = self.get_argument('detail')
998
        try:
999
            if self.container is None:
1000
                print(bold(self.client.account))
1001
                until = self.get_argument('until')
1002
                if detail:
1003
                    reply = self.client.get_account_info(until=until)
1004
                else:
1005
                    reply = self.client.get_account_meta(until=until)
1006
                    reply = pretty_keys(reply, '-')
1007
            elif self.path is None:
1008
                print(bold('%s: %s' % self.client.account, self.container))
1009
                if detail:
1010
                    reply = self.client.get_container_info(until=until)
1011
                else:
1012
                    cmeta = self.client.get_container_meta(until=until)
1013
                    ometa = self.client.get_container_object_meta(until=until)
1014
                    reply = {'container-meta': pretty_keys(cmeta, '-'),
1015
                        'object-meta': pretty_keys(ometa, '-')}
1016
            else:
1017
                print('%s: %s:%s'\
1018
                % (bold(self.client.account, self.container, self.path)))
1019
                version = self.get_argument('object_version')
1020
                if detail:
1021
                    reply = self.client.get_object_info(self.path,
1022
                        version=version)
1023
                else:
1024
                    reply = self.client.get_object_meta(self.path,
1025
                        version=version)
1026
                    reply = pretty_keys(pretty_keys(reply, '-'))
1027
        except ClientError as err:
1028
            raiseCLIError(err)
1029
        print_dict(reply)
1030

    
1031

    
1032
@command()
1033
class store_setmeta(_store_container_command):
1034
    """Set a new metadatum for account [, container [or object]]"""
1035

    
1036
    def main(self, metakey___metaval, container____path__=None):
1037
        super(self.__class__, self).main(container____path__)
1038
        try:
1039
            metakey, metavalue = metakey___metaval.split(':')
1040
        except ValueError:
1041
            raise CLIError(message='Usage:  metakey:metavalue', importance=1)
1042
        try:
1043
            if self.container is None:
1044
                self.client.set_account_meta({metakey: metavalue})
1045
            elif self.path is None:
1046
                self.client.set_container_meta({metakey: metavalue})
1047
            else:
1048
                self.client.set_object_meta(self.path, {metakey: metavalue})
1049
        except ClientError as err:
1050
            raiseCLIError(err)
1051

    
1052

    
1053
@command()
1054
class store_delmeta(_store_container_command):
1055
    """Delete an existing metadatum of account [, container [or object]]"""
1056

    
1057
    def main(self, metakey, container____path__=None):
1058
        super(self.__class__, self).main(container____path__)
1059
        try:
1060
            if self.container is None:
1061
                self.client.del_account_meta(metakey)
1062
            elif self.path is None:
1063
                self.client.del_container_meta(metakey)
1064
            else:
1065
                self.client.del_object_meta(metakey, self.path)
1066
        except ClientError as err:
1067
            raiseCLIError(err)
1068

    
1069

    
1070
@command()
1071
class store_quota(_store_account_command):
1072
    """Get  quota for account [or container]"""
1073

    
1074
    def main(self, container=None):
1075
        super(self.__class__, self).main()
1076
        try:
1077
            if container is None:
1078
                reply = self.client.get_account_quota()
1079
            else:
1080
                reply = self.client.get_container_quota(container)
1081
        except ClientError as err:
1082
            raiseCLIError(err)
1083
        print_dict(reply)
1084

    
1085

    
1086
@command()
1087
class store_setquota(_store_account_command):
1088
    """Set new quota (in KB) for account [or container]"""
1089

    
1090
    def main(self, quota, container=None):
1091
        super(self.__class__, self).main()
1092
        try:
1093
            if container is None:
1094
                self.client.set_account_quota(quota)
1095
            else:
1096
                self.client.container = container
1097
                self.client.set_container_quota(quota)
1098
        except ClientError as err:
1099
            raiseCLIError(err)
1100

    
1101

    
1102
@command()
1103
class store_versioning(_store_account_command):
1104
    """Get  versioning for account [or container ]"""
1105

    
1106
    def main(self, container=None):
1107
        super(self.__class__, self).main()
1108
        try:
1109
            if container is None:
1110
                reply = self.client.get_account_versioning()
1111
            else:
1112
                reply = self.client.get_container_versioning(container)
1113
        except ClientError as err:
1114
            raiseCLIError(err)
1115
        print_dict(reply)
1116

    
1117

    
1118
@command()
1119
class store_setversioning(_store_account_command):
1120
    """Set new versioning (auto, none) for account [or container]"""
1121

    
1122
    def main(self, versioning, container=None):
1123
        super(self.__class__, self).main()
1124
        try:
1125
            if container is None:
1126
                self.client.set_account_versioning(versioning)
1127
            else:
1128
                self.client.container = container
1129
                self.client.set_container_versioning(versioning)
1130
        except ClientError as err:
1131
            raiseCLIError(err)
1132

    
1133

    
1134
@command()
1135
class store_group(_store_account_command):
1136
    """Get user groups details for account"""
1137

    
1138
    def main(self):
1139
        super(self.__class__, self).main()
1140
        try:
1141
            reply = self.client.get_account_group()
1142
        except ClientError as err:
1143
            raiseCLIError(err)
1144
        print_dict(reply)
1145

    
1146

    
1147
@command()
1148
class store_setgroup(_store_account_command):
1149
    """Create/update a new user group on account"""
1150

    
1151
    def main(self, groupname, *users):
1152
        super(self.__class__, self).main()
1153
        try:
1154
            self.client.set_account_group(groupname, users)
1155
        except ClientError as err:
1156
            raiseCLIError(err)
1157

    
1158

    
1159
@command()
1160
class store_delgroup(_store_account_command):
1161
    """Delete a user group on an account"""
1162

    
1163
    def main(self, groupname):
1164
        super(self.__class__, self).main()
1165
        try:
1166
            self.client.del_account_group(groupname)
1167
        except ClientError as err:
1168
            raiseCLIError(err)
1169

    
1170

    
1171
@command()
1172
class store_sharers(_store_account_command):
1173
    """List the accounts that share objects with default account"""
1174

    
1175
    def __init__(self, arguments={}):
1176
        super(self.__class__, self).__init__(arguments)
1177
        self.arguments['detail'] =\
1178
            FlagArgument('show detailed output', '-l')
1179
        self.arguments['limit'] =\
1180
            IntArgument('show limited output', '--n', default=1000)
1181
        self.arguments['marker'] =\
1182
            ValueArgument('show output greater then marker', '--marker')
1183

    
1184
    def main(self):
1185
        super(self.__class__, self).main()
1186
        try:
1187
            marker = self.get_argument('marker')
1188
            accounts = self.client.get_sharing_accounts(marker=marker)
1189
        except ClientError as err:
1190
            raiseCLIError(err)
1191

    
1192
        for acc in accounts:
1193
            stdout.write(bold(acc['name']) + ' ')
1194
            if self.get_argument('detail'):
1195
                print_dict(acc, exclude='name', ident=18)
1196
        if not self.get_argument('detail'):
1197
            print
1198

    
1199

    
1200
@command()
1201
class store_versions(_store_container_command):
1202
    """Get the version list of an object"""
1203

    
1204
    def main(self, container___path):
1205
        super(store_versions, self).main(container___path)
1206
        try:
1207
            versions = self.client.get_object_versionlist(self.path)
1208
        except ClientError as err:
1209
            raise CLIError(err)
1210

    
1211
        print('%s:%s versions' % (self.container, self.path))
1212
        for vitem in versions:
1213
            t = localtime(float(vitem[1]))
1214
            vid = bold(unicode(vitem[0]))
1215
            print('\t%s \t(%s)' % (vid, strftime('%d-%m-%Y %H:%M:%S', t)))