Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ cccff590

History | View | Annotate | Download (45.2 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 CLIError, raiseCLIError
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 ProgressBarArgument
40
from kamaki.cli.commands import _command_init
41
from kamaki.clients.pithos import PithosClient, ClientError
42
from kamaki.cli.utils import bold
43
from sys import stdout
44
from time import localtime, strftime
45
from datetime import datetime as dtm
46

    
47

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

    
51

    
52
# Argument functionality
53

    
54

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

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

    
66
    @value.setter
67
    def value(self, newvalue):
68
        self._value = newvalue
69

    
70

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

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

    
88

    
89
class SharingArgument(ValueArgument):
90
    @property
91
    def value(self):
92
        return getattr(self, '_value', self.default)
93

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

    
120

    
121
class RangeArgument(ValueArgument):
122
    @property
123
    def value(self):
124
        return getattr(self, '_value', self.default)
125

    
126
    @value.setter
127
    def value(self, newvalue):
128
        if newvalue is None:
129
            self._value = self.default
130
            return
131
        (start, end) = newvalue.split('-')
132
        (start, end) = (int(start), int(end))
133
        self._value = '%s-%s' % (start, end)
134

    
135

    
136
class DateArgument(ValueArgument):
137
    DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
138
        "%A, %d-%b-%y %H:%M:%S GMT",
139
        "%a, %d %b %Y %H:%M:%S GMT"]
140

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

    
143
    @property
144
    def value(self):
145
        return getattr(self, '_value', self.default)
146

    
147
    @value.setter
148
    def value(self, newvalue):
149
        if newvalue is None:
150
            return
151
        self._value = self.format_date(newvalue)
152

    
153
    def format_date(self, datestr):
154
        for format in self.INPUT_FORMATS:
155
            try:
156
                t = dtm.strptime(datestr, format)
157
            except ValueError:
158
                continue
159
            self._value = t.strftime(self.DATE_FORMATS[0])
160
            return
161
        raise CLIError('Date Argument Error',
162
            details='%s not a valid date. correct formats:\n\t%s'\
163
            % (datestr, self.INPUT_FORMATS))
164

    
165

    
166
# Command specs
167

    
168

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

    
184

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

    
188
    def __init__(self, arguments={}):
189
        super(_store_account_command, self).__init__(arguments)
190
        self.arguments['account'] =\
191
            ValueArgument('Specify the account', '--account')
192

    
193
    def generator(self, message):
194
        return None
195

    
196
    def main(self):
197
        super(_store_account_command, self).main()
198
        if self.arguments['account'].value is not None:
199
            self.client.account = self.arguments['account'].value
200

    
201

    
202
class _store_container_command(_store_account_command):
203
    """Base class for container level storage commands"""
204

    
205
    def __init__(self, arguments={}):
206
        super(_store_container_command, self).__init__(arguments)
207
        self.arguments['container'] =\
208
            ValueArgument('Specify the container name', '--container')
209
        self.container = None
210
        self.path = None
211

    
212
    def extract_container_and_path(self,
213
        container_with_path,
214
        path_is_optional=True):
215
        assert isinstance(container_with_path, str)
216
        if ':' not in container_with_path:
217
            if self.get_argument('container') is not None:
218
                self.container = self.get_argument('container')
219
            else:
220
                self.container = self.client.container
221
            if self.container is None:
222
                self.container = container_with_path
223
            else:
224
                self.path = container_with_path
225
            if not path_is_optional and self.path is None:
226
                raise CLIError('Object path is missing\n', importance=1)
227
            return
228
        cnp = container_with_path.split(':')
229
        self.container = cnp[0]
230
        try:
231
            self.path = cnp[1]
232
        except IndexError:
233
            if path_is_optional:
234
                self.path = None
235
            else:
236
                raise CLIError('Object path is missing\n', importance=1)
237

    
238
    def main(self, container_with_path=None, path_is_optional=True):
239
        super(_store_container_command, self).main()
240
        if container_with_path is not None:
241
            self.extract_container_and_path(container_with_path,
242
                path_is_optional)
243
            self.client.container = self.container
244
        elif self.get_argument('container') is not None:
245
            self.client.container = self.get_argument('container')
246
        self.container = self.client.container
247

    
248

    
249
@command(pithos_cmds)
250
class store_list(_store_container_command):
251
    """List containers, object trees or objects in a directory
252
    """
253

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

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

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

    
347
    def main(self, container____path__=None):
348
        super(self.__class__, self).main(container____path__)
349
        try:
350
            if self.container is None:
351
                r = self.client.account_get(limit=self.get_argument('limit'),
352
                    marker=self.get_argument('marker'),
353
                    if_modified_since=self.get_argument('if_modified_since'),
354
                    if_unmodified_since=self.get_argument(\
355
                        'if_unmodified_since'),
356
                    until=self.get_argument('until'),
357
                    show_only_shared=self.get_argument('shared'))
358
                self.print_containers(r.json)
359
            else:
360
                r = self.client.container_get(limit=self.get_argument('limit'),
361
                    marker=self.get_argument('marker'),
362
                    prefix=self.get_argument('prefix'),
363
                    delimiter=self.get_argument('delimiter'),
364
                    path=self.get_argument('path'),
365
                    if_modified_since=self.get_argument('if_modified_since'),
366
                    if_unmodified_since=self.get_argument(\
367
                        'if_unmodified_since'),
368
                    until=self.get_argument('until'),
369
                    meta=self.get_argument('meta'),
370
                    show_only_shared=self.get_argument('shared'))
371
                self.print_objects(r.json)
372
        except ClientError as err:
373
            raiseCLIError(err)
374

    
375

    
376
@command(pithos_cmds)
377
class store_mkdir(_store_container_command):
378
    """Create a directory"""
379

    
380
    def main(self, container___directory):
381
        super(self.__class__,
382
            self).main(container___directory, path_is_optional=False)
383
        try:
384
            self.client.create_directory(self.path)
385
        except ClientError as err:
386
            raiseCLIError(err)
387

    
388

    
389
@command(pithos_cmds)
390
class store_create(_store_container_command):
391
    """Create a container or a directory object"""
392

    
393
    def __init__(self, arguments={}):
394
        super(self.__class__, self).__init__(arguments)
395
        self.arguments['versioning'] = \
396
            ValueArgument('set container versioning (auto/none)',
397
            '--versioning')
398
        self.arguments['quota'] =\
399
            IntArgument('set default container quota', '--quota')
400
        self.arguments['meta'] =\
401
            MetaArgument('set container metadata', '--meta')
402

    
403
    def main(self, container____directory__):
404
        super(self.__class__, self).main(container____directory__)
405
        try:
406
            if self.path is None:
407
                self.client.container_put(quota=self.get_argument('quota'),
408
                    versioning=self.get_argument('versioning'),
409
                    metadata=self.get_argument('meta'))
410
            else:
411
                self.client.create_directory(self.path)
412
        except ClientError as err:
413
            raiseCLIError(err)
414

    
415

    
416
@command(pithos_cmds)
417
class store_copy(_store_container_command):
418
    """Copy an object"""
419

    
420
    def __init__(self, arguments={}):
421
        super(self.__class__, self).__init__(arguments)
422
        self.arguments['source_version'] = ValueArgument(\
423
            'copy specific version', '--source-version')
424
        self.arguments['public'] = ValueArgument(\
425
            'make object publicly accessible', '--public')
426
        self.arguments['content_type'] = ValueArgument(\
427
            'change object\'s content type', '--content-type')
428
        self.arguments['delimiter'] = DelimiterArgument(self,
429
            parsed_name='--delimiter',
430
            help=u'copy objects prefixed as src_object + delimiter')
431
        self.arguments['recursive'] = FlagArgument(
432
            'mass copy with delimiter /', ('-r', '--recursive'))
433

    
434
    def main(self, source_container___path, destination_container____path__):
435
        super(self.__class__,
436
            self).main(source_container___path, path_is_optional=False)
437
        try:
438
            dst = destination_container____path__.split(':')
439
            dst_cont = dst[0]
440
            dst_path = dst[1] if len(dst) > 1 else False
441
            self.client.copy_object(src_container=self.container,
442
                src_object=self.path,
443
                dst_container=dst_cont,
444
                dst_object=dst_path,
445
                source_version=self.get_argument('source_version'),
446
                public=self.get_argument('public'),
447
                content_type=self.get_argument('content_type'),
448
                delimiter=self.get_argument('delimiter'))
449
        except ClientError as err:
450
            raiseCLIError(err)
451

    
452

    
453
@command(pithos_cmds)
454
class store_move(_store_container_command):
455
    """Copy an object"""
456

    
457
    def __init__(self, arguments={}):
458
        super(self.__class__, self).__init__(arguments)
459

    
460
        self.arguments['source_version'] = ValueArgument(\
461
            'copy specific version', '--source-version')
462
        self.arguments['public'] = FlagArgument(\
463
            'make object publicly accessible', '--public')
464
        self.arguments['content_type'] = ValueArgument(\
465
            'change object\'s content type', '--content-type')
466
        self.arguments['delimiter'] = DelimiterArgument(self,
467
            parsed_name='--delimiter',
468
            help='move objects prefixed as src_object + delimiter')
469
        self.arguments['recursive'] = FlagArgument(\
470
            'copy with delimiter /', ('-r', '--recursive'))
471

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

    
490

    
491
@command(pithos_cmds)
492
class store_append(_store_container_command):
493
    """Append local file to (existing) remote object"""
494

    
495
    def __init__(self, arguments={}):
496
        super(self.__class__, self).__init__(arguments)
497
        self.arguments['progress_bar'] = ProgressBarArgument(\
498
            'do not show progress bar', '--no-progress-bar', False)
499

    
500
    def main(self, local_path, container___path):
501
        super(self.__class__,
502
            self).main(container___path, path_is_optional=False)
503
        try:
504
            f = open(local_path, 'r')
505
            try:
506
                progress_bar = self.arguments['progress_bar']
507
                upload_cb = progress_bar.get_generator('Appending blocks')
508
            except Exception:
509
                upload_cb = None
510
            self.client.append_object(object=self.path,
511
                source_file=f,
512
                upload_cb=upload_cb)
513
        except ClientError as err:
514
            raiseCLIError(err)
515

    
516

    
517
@command(pithos_cmds)
518
class store_truncate(_store_container_command):
519
    """Truncate remote file up to a size"""
520

    
521
    def main(self, container___path, size=0):
522
        super(self.__class__,
523
            self).main(container___path, path_is_optional=False)
524
        try:
525
            self.client.truncate_object(self.path, size)
526
        except ClientError as err:
527
            raiseCLIError(err)
528

    
529

    
530
@command(pithos_cmds)
531
class store_overwrite(_store_container_command):
532
    """Overwrite part (from start to end) of a remote file"""
533

    
534
    def __init__(self, arguments={}):
535
        super(self.__class__, self).__init__(arguments)
536
        self.arguments['progress_bar'] = ProgressBarArgument(\
537
            'do not show progress bar', '--no-progress-bar', False)
538

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

    
557

    
558
@command(pithos_cmds)
559
class store_manifest(_store_container_command):
560
    """Create a remote file with uploaded parts by manifestation"""
561

    
562
    def __init__(self, arguments={}):
563
        super(self.__class__, self).__init__(arguments)
564
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
565
        self.arguments['content_encoding'] = ValueArgument(\
566
            'provide the object MIME content type', '--content-encoding')
567
        self.arguments['content_disposition'] = ValueArgument(\
568
            'provide the presentation style of the object',
569
            '--content-disposition')
570
        self.arguments['content_type'] = ValueArgument(\
571
            'create object with specific content type', '--content-type')
572
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
573
            help='define object sharing policy ' +\
574
            '( "read=user1,grp1,user2,... write=user1,grp2,..." )')
575
        self.arguments['public'] = FlagArgument(\
576
            'make object publicly accessible', '--public')
577

    
578
    def main(self, container___path):
579
        super(self.__class__,
580
            self).main(container___path, path_is_optional=False)
581
        try:
582
            self.client.create_object_by_manifestation(self.path,
583
                content_encoding=self.get_argument('content_encoding'),
584
                content_disposition=self.get_argument('content_disposition'),
585
                content_type=self.get_argument('content_type'),
586
                sharing=self.get_argument('sharing'),
587
                public=self.get_argument('public'))
588
        except ClientError as err:
589
            raiseCLIError(err)
590

    
591

    
592
@command(pithos_cmds)
593
class store_upload(_store_container_command):
594
    """Upload a file"""
595

    
596
    def __init__(self, arguments={}):
597
        super(self.__class__, self).__init__(arguments)
598
        self.arguments['use_hashes'] = FlagArgument(\
599
            'provide hashmap file instead of data', '--use-hashes')
600
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
601
        self.arguments['unchunked'] = FlagArgument(\
602
            'avoid chunked transfer mode', '--unchunked')
603
        self.arguments['content_encoding'] = ValueArgument(\
604
            'provide the object MIME content type', '--content-encoding')
605
        self.arguments['content_disposition'] = ValueArgument(\
606
            'provide the presentation style of the object',
607
            '--content-disposition')
608
        self.arguments['content_type'] = ValueArgument(\
609
            'create object with specific content type', '--content-type')
610
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
611
            help='define sharing object policy ' +\
612
            '( "read=user1,grp1,user2,... write=user1,grp2,...')
613
        self.arguments['public'] = FlagArgument(\
614
            'make object publicly accessible', '--public')
615
        self.arguments['poolsize'] = IntArgument(\
616
            'set pool size', '--with-pool-size')
617
        self.arguments['progress_bar'] = ProgressBarArgument(\
618
            'do not show progress bar', '--no-progress-bar', False)
619

    
620
    def main(self, local_path, container____path__):
621
        super(self.__class__, self).main(container____path__)
622
        remote_path = local_path if self.path is None else self.path
623
        poolsize = self.get_argument('poolsize')
624
        if poolsize is not None:
625
            self.client.POOL_SIZE = int(poolsize)
626
        params = dict(content_encoding=self.get_argument('content_encoding'),
627
            content_type=self.get_argument('content_type'),
628
            content_disposition=self.get_argument('content_disposition'),
629
            sharing=self.get_argument('sharing'),
630
            public=self.get_argument('public'))
631
        try:
632
            with open(local_path) as f:
633
                if self.get_argument('unchunked'):
634
                    self.client.upload_object_unchunked(remote_path, f,
635
                    etag=self.get_argument('etag'),
636
                    withHashFile=self.get_argument('use_hashes'),
637
                    **params)
638
                else:
639
                    progress_bar = self.arguments['progress_bar']
640
                    hash_cb = progress_bar.get_generator(\
641
                        'Calculating block hashes')
642
                    upload_cb = progress_bar.get_generator('Uploading')
643
                    self.client.upload_object(remote_path, f,
644
                    hash_cb=hash_cb,
645
                    upload_cb=upload_cb,
646
                    **params)
647
        except ClientError as err:
648
            raiseCLIError(err)
649
        except IOError as err:
650
            raise CLIError(message='Failed to read form file %s' % local_path,
651
                importance=2,
652
                details=unicode(err))
653
        print 'Upload completed'
654

    
655

    
656
@command(pithos_cmds)
657
class store_cat(_store_container_command):
658
    """Print a file to console"""
659

    
660
    def __init__(self, arguments={}):
661
        super(self.__class__, self).__init__(arguments)
662
        self.arguments['range'] =\
663
            RangeArgument('show range of data', '--range')
664
        self.arguments['if_match'] =\
665
            ValueArgument('show output if ETags match', '--if-match')
666
        self.arguments['if_none_match'] =\
667
            ValueArgument('show output if ETags match', '--if-none-match')
668
        self.arguments['if_modified_since'] =\
669
            DateArgument('show output modified since then',
670
            '--if-modified-since')
671
        self.arguments['if_unmodified_since'] =\
672
            DateArgument('show output unmodified since then',
673
            '--if-unmodified-since')
674
        self.arguments['object_version'] =\
675
            ValueArgument('get the specific version', '--object-version')
676

    
677
    def main(self, container___path):
678
        super(self.__class__,
679
            self).main(container___path, path_is_optional=False)
680
        try:
681
            self.client.download_object(self.path, stdout,
682
            range=self.get_argument('range'),
683
            version=self.get_argument('object_version'),
684
            if_match=self.get_argument('if_match'),
685
            if_none_match=self.get_argument('if_none_match'),
686
            if_modified_since=self.get_argument('if_modified_since'),
687
            if_unmodified_since=self.get_argument('if_unmodified_since'))
688
        except ClientError as err:
689
            raiseCLIError(err)
690

    
691

    
692
@command(pithos_cmds)
693
class store_download(_store_container_command):
694
    """Download a file"""
695

    
696
    def __init__(self, arguments={}):
697
        super(self.__class__, self).__init__(arguments)
698
        self.arguments['resume'] = FlagArgument(parsed_name='--resume',
699
            help='Resume a previous download instead of overwritting it')
700
        self.arguments['range'] = RangeArgument(\
701
            'show range of data', '--range')
702
        self.arguments['if_match'] = ValueArgument(\
703
            'show output if ETags match', '--if-match')
704
        self.arguments['if_none_match'] = ValueArgument(\
705
            'show output if ETags match', '--if-none-match')
706
        self.arguments['if_modified_since'] = DateArgument(\
707
            'show output modified since then', '--if-modified-since')
708
        self.arguments['if_unmodified_since'] = DateArgument(\
709
            'show output unmodified since then', '--if-unmodified-since')
710
        self.arguments['object_version'] = ValueArgument(\
711
            'get the specific version', '--object-version')
712
        self.arguments['poolsize'] = IntArgument(\
713
            'set pool size', '--with-pool-size')
714
        self.arguments['progress_bar'] = ProgressBarArgument(\
715
            'do not show progress bar', '--no-progress-bar', False)
716

    
717
    def main(self, container___path, local_path):
718
        super(self.__class__,
719
            self).main(container___path, path_is_optional=False)
720

    
721
        # setup output stream
722
        if local_path is None:
723
            out = stdout
724
        else:
725
            try:
726
                if self.get_argument('resume'):
727
                    out = open(local_path, 'rwb+')
728
                else:
729
                    out = open(local_path, 'wb+')
730
            except IOError as err:
731
                raise CLIError(message='Cannot write to file %s - %s'\
732
                    % (local_path, unicode(err)),
733
                    importance=1)
734
        progress_bar = self.arguments['progress_bar']
735
        download_cb = progress_bar.get_generator('Downloading')
736
        poolsize = self.get_argument('poolsize')
737
        if poolsize is not None:
738
            self.client.POOL_SIZE = int(poolsize)
739

    
740
        try:
741
            self.client.download_object(self.path, out,
742
                download_cb=download_cb,
743
                range=self.get_argument('range'),
744
                version=self.get_argument('object_version'),
745
                if_match=self.get_argument('if_match'),
746
                resume=self.get_argument('resume'),
747
                if_none_match=self.get_argument('if_none_match'),
748
                if_modified_since=self.get_argument('if_modified_since'),
749
                if_unmodified_since=self.get_argument('if_unmodified_since'))
750
        except ClientError as err:
751
            raiseCLIError(err)
752
        except KeyboardInterrupt:
753
            from threading import enumerate as activethreads
754
            stdout.write('\nFinishing active threads ')
755
            for thread in activethreads():
756
                stdout.flush()
757
                try:
758
                    thread.join()
759
                    stdout.write('.')
760
                except RuntimeError:
761
                    continue
762
            print('\ndownload canceled by user')
763
            if local_path is not None:
764
                print('to resume, re-run with --resume')
765
        print
766

    
767

    
768
@command(pithos_cmds)
769
class store_hashmap(_store_container_command):
770
    """Get the hashmap of an object"""
771

    
772
    def __init__(self, arguments={}):
773
        super(self.__class__, self).__init__(arguments)
774
        self.arguments['if_match'] =\
775
            ValueArgument('show output if ETags match', '--if-match')
776
        self.arguments['if_none_match'] =\
777
            ValueArgument('show output if ETags match', '--if-none-match')
778
        self.arguments['if_modified_since'] =\
779
            DateArgument('show output modified since then',
780
            '--if-modified-since')
781
        self.arguments['if_unmodified_since'] =\
782
            DateArgument('show output unmodified since then',
783
            '--if-unmodified-since')
784
        self.arguments['object_version'] =\
785
            ValueArgument('get the specific version', '--object-version')
786

    
787
    def main(self, container___path):
788
        super(self.__class__,
789
            self).main(container___path, path_is_optional=False)
790
        try:
791
            data = self.client.get_object_hashmap(self.path,
792
                version=self.arguments('object_version'),
793
                if_match=self.arguments('if_match'),
794
                if_none_match=self.arguments('if_none_match'),
795
                if_modified_since=self.arguments('if_modified_since'),
796
                if_unmodified_since=self.arguments('if_unmodified_since'))
797
        except ClientError as err:
798
            raiseCLIError(err)
799
        print_dict(data)
800

    
801

    
802
@command(pithos_cmds)
803
class store_delete(_store_container_command):
804
    """Delete a container [or an object]"""
805

    
806
    def __init__(self, arguments={}):
807
        super(self.__class__, self).__init__(arguments)
808
        self.arguments['until'] = DateArgument(\
809
            'remove history until that date', '--until')
810
        self.arguments['recursive'] = FlagArgument(\
811
            'empty dir or container and delete (if dir)',
812
            ('-r', '--recursive'))
813
        self.arguments['delimiter'] = DelimiterArgument(self,
814
            parsed_name='--delimiter',
815
            help='delete objects prefixed with <object><delimiter>')
816

    
817
    def main(self, container____path__):
818
        super(self.__class__, self).main(container____path__)
819
        try:
820
            if self.path is None:
821
                self.client.del_container(until=self.get_argument('until'),
822
                    delimiter=self.get_argument('delimiter'))
823
            else:
824
                # self.client.delete_object(self.path)
825
                self.client.del_object(self.path,
826
                    until=self.get_argument('until'),
827
                    delimiter=self.get_argument('delimiter'))
828
        except ClientError as err:
829
            raiseCLIError(err)
830

    
831

    
832
@command(pithos_cmds)
833
class store_purge(_store_container_command):
834
    """Purge a container"""
835

    
836
    def main(self, container):
837
        super(self.__class__, self).main(container)
838
        try:
839
            self.client.purge_container()
840
        except ClientError as err:
841
            raiseCLIError(err)
842

    
843

    
844
@command(pithos_cmds)
845
class store_publish(_store_container_command):
846
    """Publish an object"""
847

    
848
    def main(self, container___path):
849
        super(self.__class__,
850
            self).main(container___path, path_is_optional=False)
851
        try:
852
            self.client.publish_object(self.path)
853
        except ClientError as err:
854
            raiseCLIError(err)
855

    
856

    
857
@command(pithos_cmds)
858
class store_unpublish(_store_container_command):
859
    """Unpublish an object"""
860

    
861
    def main(self, container___path):
862
        super(self.__class__,
863
            self).main(container___path, path_is_optional=False)
864
        try:
865
            self.client.unpublish_object(self.path)
866
        except ClientError as err:
867
            raiseCLIError(err)
868

    
869

    
870
@command(pithos_cmds)
871
class store_permissions(_store_container_command):
872
    """Get object read/write permissions """
873

    
874
    def main(self, container___path):
875
        super(self.__class__,
876
            self).main(container___path, path_is_optional=False)
877
        try:
878
            reply = self.client.get_object_sharing(self.path)
879
            print_dict(reply)
880
        except ClientError as err:
881
            raiseCLIError(err)
882

    
883

    
884
@command(pithos_cmds)
885
class store_setpermissions(_store_container_command):
886
    """Set sharing permissions """
887

    
888
    def format_permition_dict(self, permissions):
889
        read = False
890
        write = False
891
        for perms in permissions:
892
            splstr = perms.split('=')
893
            if 'read' == splstr[0]:
894
                read = [user_or_group.strip() \
895
                for user_or_group in splstr[1].split(',')]
896
            elif 'write' == splstr[0]:
897
                write = [user_or_group.strip() \
898
                for user_or_group in splstr[1].split(',')]
899
            else:
900
                read = False
901
                write = False
902
        if not read and not write:
903
            raise CLIError(importance=0,
904
                message='Usage:\tread=<groups,users> write=<groups,users>')
905
        return (read, write)
906

    
907
    def main(self, container___path, *permissions):
908
        super(self.__class__,
909
            self).main(container___path, path_is_optional=False)
910
        (read, write) = self.format_permition_dict(permissions)
911
        try:
912
            self.client.set_object_sharing(self.path,
913
                read_permition=read, write_permition=write)
914
        except ClientError as err:
915
            raiseCLIError(err)
916

    
917

    
918
@command(pithos_cmds)
919
class store_delpermissions(_store_container_command):
920
    """Delete all sharing permissions"""
921

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

    
930

    
931
@command(pithos_cmds)
932
class store_info(_store_container_command):
933
    """Get information for account [, container [or object]]"""
934

    
935
    def __init__(self, arguments={}):
936
        super(self.__class__, self).__init__(arguments)
937
        self.arguments['object_version'] =\
938
            ValueArgument(parsed_name='--object-version',
939
            help='show specific version \ (applies only for objects)')
940

    
941
    def main(self, container____path__=None):
942
        super(self.__class__, self).main(container____path__)
943
        try:
944
            if self.container is None:
945
                reply = self.client.get_account_info()
946
            elif self.path is None:
947
                reply = self.client.get_container_info(self.container)
948
            else:
949
                reply = self.client.get_object_info(self.path,
950
                    version=self.get_argument('object_version'))
951
        except ClientError as err:
952
            raiseCLIError(err)
953
        print_dict(reply)
954

    
955

    
956
@command(pithos_cmds)
957
class store_meta(_store_container_command):
958
    """Get custom meta-content for account [, container [or object]]"""
959

    
960
    def __init__(self, arguments={}):
961
        super(self.__class__, self).__init__(arguments)
962
        self.arguments['detail'] =\
963
            FlagArgument('show detailed output', '-l')
964
        self.arguments['until'] =\
965
            DateArgument('show metadata until then', '--until')
966
        self.arguments['object_version'] =\
967
            ValueArgument(parsed_name='--object-version',
968
            help='show specific version \ (applies only for objects)')
969

    
970
    def main(self, container____path__=None):
971
        super(self.__class__, self).main(container____path__)
972

    
973
        detail = self.get_argument('detail')
974
        try:
975
            until = self.get_argument('until')
976
            if self.container is None:
977
                print(bold(self.client.account))
978
                if detail:
979
                    reply = self.client.get_account_info(until=until)
980
                else:
981
                    reply = self.client.get_account_meta(until=until)
982
                    reply = pretty_keys(reply, '-')
983
            elif self.path is None:
984
                print(bold('%s: %s' % (self.client.account, self.container)))
985
                if detail:
986
                    reply = self.client.get_container_info(until=until)
987
                else:
988
                    cmeta = self.client.get_container_meta(until=until)
989
                    ometa = self.client.get_container_object_meta(until=until)
990
                    reply = {'container-meta': pretty_keys(cmeta, '-'),
991
                        'object-meta': pretty_keys(ometa, '-')}
992
            else:
993
                print(bold('%s: %s:%s'\
994
                    % (self.client.account, self.container, self.path)))
995
                version = self.get_argument('object_version')
996
                if detail:
997
                    reply = self.client.get_object_info(self.path,
998
                        version=version)
999
                else:
1000
                    reply = self.client.get_object_meta(self.path,
1001
                        version=version)
1002
                    reply = pretty_keys(pretty_keys(reply, '-'))
1003
        except ClientError as err:
1004
            raiseCLIError(err)
1005
        print_dict(reply)
1006

    
1007

    
1008
@command(pithos_cmds)
1009
class store_setmeta(_store_container_command):
1010
    """Set a new metadatum for account [, container [or object]]"""
1011

    
1012
    def main(self, metakey___metaval, container____path__=None):
1013
        super(self.__class__, self).main(container____path__)
1014
        try:
1015
            metakey, metavalue = metakey___metaval.split(':')
1016
        except ValueError:
1017
            raise CLIError(message='Usage:  metakey:metavalue', importance=1)
1018
        try:
1019
            if self.container is None:
1020
                self.client.set_account_meta({metakey: metavalue})
1021
            elif self.path is None:
1022
                self.client.set_container_meta({metakey: metavalue})
1023
            else:
1024
                self.client.set_object_meta(self.path, {metakey: metavalue})
1025
        except ClientError as err:
1026
            raiseCLIError(err)
1027

    
1028

    
1029
@command(pithos_cmds)
1030
class store_delmeta(_store_container_command):
1031
    """Delete an existing metadatum of account [, container [or object]]"""
1032

    
1033
    def main(self, metakey, container____path__=None):
1034
        super(self.__class__, self).main(container____path__)
1035
        try:
1036
            if self.container is None:
1037
                self.client.del_account_meta(metakey)
1038
            elif self.path is None:
1039
                self.client.del_container_meta(metakey)
1040
            else:
1041
                self.client.del_object_meta(metakey, self.path)
1042
        except ClientError as err:
1043
            raiseCLIError(err)
1044

    
1045

    
1046
@command(pithos_cmds)
1047
class store_quota(_store_account_command):
1048
    """Get  quota for account [or container]"""
1049

    
1050
    def main(self, container=None):
1051
        super(self.__class__, self).main()
1052
        try:
1053
            if container is None:
1054
                reply = self.client.get_account_quota()
1055
            else:
1056
                reply = self.client.get_container_quota(container)
1057
        except ClientError as err:
1058
            raiseCLIError(err)
1059
        print_dict(reply)
1060

    
1061

    
1062
@command(pithos_cmds)
1063
class store_setquota(_store_account_command):
1064
    """Set new quota (in KB) for account [or container]"""
1065

    
1066
    def main(self, quota, container=None):
1067
        super(self.__class__, self).main()
1068
        try:
1069
            if container is None:
1070
                self.client.set_account_quota(quota)
1071
            else:
1072
                self.client.container = container
1073
                self.client.set_container_quota(quota)
1074
        except ClientError as err:
1075
            raiseCLIError(err)
1076

    
1077

    
1078
@command(pithos_cmds)
1079
class store_versioning(_store_account_command):
1080
    """Get  versioning for account [or container ]"""
1081

    
1082
    def main(self, container=None):
1083
        super(self.__class__, self).main()
1084
        try:
1085
            if container is None:
1086
                reply = self.client.get_account_versioning()
1087
            else:
1088
                reply = self.client.get_container_versioning(container)
1089
        except ClientError as err:
1090
            raiseCLIError(err)
1091
        print_dict(reply)
1092

    
1093

    
1094
@command(pithos_cmds)
1095
class store_setversioning(_store_account_command):
1096
    """Set new versioning (auto, none) for account [or container]"""
1097

    
1098
    def main(self, versioning, container=None):
1099
        super(self.__class__, self).main()
1100
        try:
1101
            if container is None:
1102
                self.client.set_account_versioning(versioning)
1103
            else:
1104
                self.client.container = container
1105
                self.client.set_container_versioning(versioning)
1106
        except ClientError as err:
1107
            raiseCLIError(err)
1108

    
1109

    
1110
@command(pithos_cmds)
1111
class store_group(_store_account_command):
1112
    """Get user groups details for account"""
1113

    
1114
    def main(self):
1115
        super(self.__class__, self).main()
1116
        try:
1117
            reply = self.client.get_account_group()
1118
        except ClientError as err:
1119
            raiseCLIError(err)
1120
        print_dict(reply)
1121

    
1122

    
1123
@command(pithos_cmds)
1124
class store_setgroup(_store_account_command):
1125
    """Create/update a new user group on account"""
1126

    
1127
    def main(self, groupname, *users):
1128
        super(self.__class__, self).main()
1129
        try:
1130
            self.client.set_account_group(groupname, users)
1131
        except ClientError as err:
1132
            raiseCLIError(err)
1133

    
1134

    
1135
@command(pithos_cmds)
1136
class store_delgroup(_store_account_command):
1137
    """Delete a user group on an account"""
1138

    
1139
    def main(self, groupname):
1140
        super(self.__class__, self).main()
1141
        try:
1142
            self.client.del_account_group(groupname)
1143
        except ClientError as err:
1144
            raiseCLIError(err)
1145

    
1146

    
1147
@command(pithos_cmds)
1148
class store_sharers(_store_account_command):
1149
    """List the accounts that share objects with default account"""
1150

    
1151
    def __init__(self, arguments={}):
1152
        super(self.__class__, self).__init__(arguments)
1153
        self.arguments['detail'] =\
1154
            FlagArgument('show detailed output', '-l')
1155
        self.arguments['limit'] =\
1156
            IntArgument('show limited output', '--n', default=1000)
1157
        self.arguments['marker'] =\
1158
            ValueArgument('show output greater then marker', '--marker')
1159

    
1160
    def main(self):
1161
        super(self.__class__, self).main()
1162
        try:
1163
            marker = self.get_argument('marker')
1164
            accounts = self.client.get_sharing_accounts(marker=marker)
1165
        except ClientError as err:
1166
            raiseCLIError(err)
1167

    
1168
        for acc in accounts:
1169
            stdout.write(bold(acc['name']) + ' ')
1170
            if self.get_argument('detail'):
1171
                print_dict(acc, exclude='name', ident=4)
1172
        if not self.get_argument('detail'):
1173
            print
1174

    
1175

    
1176
@command(pithos_cmds)
1177
class store_versions(_store_container_command):
1178
    """Get the version list of an object"""
1179

    
1180
    def main(self, container___path):
1181
        super(store_versions, self).main(container___path)
1182
        try:
1183
            versions = self.client.get_object_versionlist(self.path)
1184
        except ClientError as err:
1185
            raise CLIError(err)
1186

    
1187
        print('%s:%s versions' % (self.container, self.path))
1188
        for vitem in versions:
1189
            t = localtime(float(vitem[1]))
1190
            vid = bold(unicode(vitem[0]))
1191
            print('\t%s \t(%s)' % (vid, strftime('%d-%m-%Y %H:%M:%S', t)))