Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 0f653327

History | View | Annotate | Download (42.3 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#, set_api_description
35
from kamaki.clients.utils import filter_in
36
from kamaki.cli.errors import CLIError, raiseCLIError
37
from kamaki.cli.utils import format_size, print_dict, pretty_keys, print_list
38
from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
39
#set_api_description('store', 'Pithos+ storage commands')
40
API_DESCRIPTION = dict(store='Pithos+ storage commands')
41
from kamaki.clients.pithos import PithosClient, ClientError
42
from colors import bold
43
from sys import stdout, exit
44
import signal
45
from time import localtime, strftime, strptime, mktime
46
from datetime import datetime as dtm
47

    
48
from progress.bar import IncrementalBar
49

    
50
#Argument functionality
51
class DelimiterArgument(ValueArgument):
52
    def __init__(self, caller_obj, help='', parsed_name=None, default=None):
53
        super(DelimiterArgument, self).__init__(help, parsed_name, default)
54
        self.caller_obj = caller_obj
55

    
56
    @property 
57
    def value(self):
58
        if self.caller_obj.get_argument('recursive'):
59
            return '/'
60
        return getattr(self, '_value', self.default)
61
    @value.setter 
62
    def value(self, newvalue):
63
        self._value = newvalue
64

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

    
81

    
82

    
83
class ProgressBarArgument(FlagArgument):
84

    
85
    def __init__(self, help='', parsed_name='', default=True):
86
        self.suffix = '%(percent)d%%'
87
        super(ProgressBarArgument, self).__init__(help, parsed_name, default)
88
        self.bar = IncrementalBar()
89

    
90
    @property 
91
    def value(self):
92
        return getattr(self, '_value', self.default)
93
    @value.setter 
94
    def value(self, newvalue):
95
        """By default, it is on (True)"""
96
        self._value = not newvalue
97
    def get_generator(self, message, message_len=25):
98
        bar = ProgressBar()
99
        return bar.get_generator(message, message_len)
100

    
101
class ProgressBar(IncrementalBar):
102
    def get_generator(self, message, message_len):
103
        def progress_gen(n):
104
            self.msg = message.ljust(message_len)
105
            for i in self.iter(range(n)):
106
                yield
107
            yield
108
        return progress_gen
109

    
110
class SharingArgument(ValueArgument):
111
    @property 
112
    def value(self):
113
        return getattr(self, '_value', self.default)
114
    @value.setter
115
    def value(self, newvalue):
116
        perms = {}
117
        try:
118
            permlist = newvalue.split(' ')
119
        except AttributeError:
120
            return
121
        for p in permlist:
122
            try:
123
                (key,val) = p.split('=')
124
            except ValueError:
125
                raise CLIError(message='Error in --sharing', details='Incorrect format', importance=1)
126
            if key.lower() not in ('read', 'write'):
127
                raise CLIError(message='Error in --sharing', details='Invalid permition key %s'%key, importance=1)
128
            val_list = val.split(',')
129
            if not perms.has_key(key):
130
                perms[key]=[]
131
            for item in val_list:
132
                if item not in perms[key]:
133
                    perms[key].append(item)
134
        self._value = perms
135

    
136
class RangeArgument(ValueArgument):
137
    @property 
138
    def value(self):
139
        return getattr(self, '_value', self.default)
140
    @value.setter
141
    def value(self, newvalue):
142
        if newvalue is None:
143
            self._value = self.default
144
            return
145
        (start, end) = newvalue.split('_')
146
        (start, end) = (int(start), int(end))
147
        self._value = '%s-%s'%(start, end)
148

    
149
class DateArgument(ValueArgument):
150
    DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
151
        "%A, %d-%b-%y %H:%M:%S GMT",
152
        "%a, %d %b %Y %H:%M:%S GMT"]
153

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

    
156
    @property 
157
    def value(self):
158
        return getattr(self, '_value', self.default)
159
    @value.setter
160
    def value(self, newvalue):
161
        if newvalue is None:
162
            return
163
        self._value = self.format_date(newvalue)
164

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

    
176
#Command specs
177
class _pithos_init(object):
178
    def __init__(self, arguments={}):
179
        self.arguments = arguments
180
        try:
181
            self.config = self.get_argument('config')
182
        except KeyError:
183
            pass
184

    
185
    def get_argument(self, arg_name):
186
        return self.arguments[arg_name].value
187

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

    
197
class _store_account_command(_pithos_init):
198
    """Base class for account level storage commands"""
199

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

    
204
    def generator(self, message):
205
       return None 
206

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

    
212
class _store_container_command(_store_account_command):
213
    """Base class for container level storage commands"""
214

    
215
    def __init__(self, arguments={}):
216
        super(_store_container_command, self).__init__(arguments)
217
        self.arguments['container'] = ValueArgument('Specify the container name', '--container')
218
        self.container = None
219
        self.path = None
220

    
221
    def extract_container_and_path(self, container_with_path, path_is_optional=True):
222
        assert isinstance(container_with_path, str)
223
        if ':' not in container_with_path:
224
            if self.get_argument('container') is not None:
225
                self.container = self.get_argument('container')
226
            else:
227
                self.container = self.client.container
228
            if self.container is None:
229
                self.container = container_with_path
230
            else:
231
                self.path = container_with_path
232
            if not path_is_optional and self.path is None:
233
                raise CLIError(message="Object path is missing", status=11)
234
            return
235
        cnp = container_with_path.split(':')
236
        self.container = cnp[0]
237
        try:
238
            self.path = cnp[1]
239
        except IndexError:
240
            if path_is_optional:
241
                self.path = None
242
            else:
243
                raise CLIError(message="Object path is missing", status=11)
244

    
245
    def main(self, container_with_path=None, path_is_optional=True):
246
        super(_store_container_command, self).main()
247
        if container_with_path is not None:
248
            self.extract_container_and_path(container_with_path, path_is_optional)
249
            self.client.container = self.container
250
        elif self.get_argument('container') is not None:
251
            self.client.container = self.get_argument('container')
252
        self.container = self.client.container
253

    
254
"""
255
@command()
256
class store_test(_store_container_command):
257
    "Test stuff something""
258

259
    def main(self):
260
        super(self.__class__, self).main('pithos')
261
        r = self.client.container_get()
262
        print(unicode(r.content)+' '+unicode(r.json))
263
"""
264

    
265
@command()
266
class store_list(_store_container_command):
267
    """List containers, object trees or objects in a directory
268
    """
269

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

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

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

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

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

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

    
386
@command()
387
class store_create(_store_container_command):
388
    """Create a container or a directory object"""
389

    
390

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

    
398
    def main(self, container____directory__):
399
        super(self.__class__, self).main(container____directory__)
400
        try:
401
            if self.path is None:
402
                self.client.container_put(quota=self.get_argument('quota'),
403
                    versioning=self.get_argument('versioning'),
404
                    metadata=self.get_argument('meta'))
405
            else:
406
                self.client.create_directory(self.path)
407
        except ClientError as err:
408
            raiseCLIError(err)
409

    
410
@command()
411
class store_copy(_store_container_command):
412
    """Copy an object"""
413

    
414
    def __init__(self, arguments={}):
415
        super(self.__class__, self).__init__(arguments)
416
        self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
417
        self.arguments['public']=ValueArgument('make object publicly accessible', '--public')
418
        self.arguments['content_type']=ValueArgument('change object\'s content type',
419
            '--content-type')
420
        self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
421
            help = u'mass copy objects with path staring with src_object + delimiter')
422
        self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
423

    
424
    def main(self, source_container___path, destination_container____path__):
425
        super(self.__class__, self).main(source_container___path, path_is_optional=False)
426
        try:
427
            dst = destination_container____path__.split(':')
428
            dst_cont = dst[0]
429
            dst_path = dst[1] if len(dst) > 1 else False
430
            self.client.copy_object(src_container = self.container, src_object = self.path,
431
                dst_container = dst_cont, dst_object = dst_path,
432
                source_version=self.get_argument('source_version'),
433
                public=self.get_argument('public'),
434
                content_type=self.get_argument('content_type'),
435
                delimiter=self.get_argument('delimiter'))
436
        except ClientError as err:
437
            raiseCLIError(err)
438

    
439
@command()
440
class store_move(_store_container_command):
441
    """Copy an object"""
442

    
443
    def __init__(self, arguments={}):
444
        super(self.__class__, self).__init__(arguments)
445

    
446
        self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
447
        self.arguments['public']=FlagArgument('make object publicly accessible', '--public')
448
        self.arguments['content_type']=ValueArgument('change object\'s content type',
449
            '--content-type')
450
        self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
451
            help = u'mass copy objects with path staring with src_object + delimiter')
452
        self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
453

    
454
    def main(self, source_container___path, destination_container____path__):
455
        super(self.__class__, self).main(source_container___path, path_is_optional=False)
456
        try:
457
            dst = destination_container____path__.split(':')
458
            dst_cont = dst[0]
459
            dst_path = dst[1] if len(dst) > 1 else False
460
            self.client.move_object(src_container = self.container, src_object = self.path,
461
                dst_container = dst_cont, dst_object = dst_path,
462
                source_version=self.get_argument('source_version'),
463
                public=self.get_argument('public'),
464
                content_type=self.get_argument('content_type'),
465
                delimiter=self.get_argument('delimiter'))
466
        except ClientError as err:
467
            raiseCLIError(err)
468

    
469
@command()
470
class store_append(_store_container_command):
471
    """Append local file to (existing) remote object"""
472

    
473
    def __init__(self, arguments={}):
474
        super(self.__class__, self).__init__(arguments)
475
        self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar', '--no-progress-bar')
476

    
477
    def main(self, local_path, container___path):
478
        super(self.__class__, self).main(container___path, path_is_optional=False)
479
        try:
480
            f = open(local_path, 'r')
481
            upload_cb = self.arguments['progress_bar'].get_generator('Appending blocks')
482
            self.client.append_object(object=self.path, source_file = f, upload_cb = upload_cb)
483
        except ClientError as err:
484
            raiseCLIError(err)
485

    
486
@command()
487
class store_truncate(_store_container_command):
488
    """Truncate remote file up to a size"""
489

    
490
    def main(self, container___path, size=0):
491
        super(self.__class__, self).main(container___path, path_is_optional=False)
492
        try:
493
            self.client.truncate_object(self.path, size)
494
        except ClientError as err:
495
            raiseCLIError(err)
496

    
497
@command()
498
class store_overwrite(_store_container_command):
499
    """Overwrite part (from start to end) of a remote file"""
500

    
501
    def __init__(self, arguments={}):
502
        super(self.__class__, self).__init__(arguments)
503
        self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
504
            '--no-progress-bar')
505

    
506
    def main(self, local_path, container___path, start, end):
507
        super(self.__class__, self).main(container___path, path_is_optional=False)
508
        try:
509
            f = open(local_path, 'r')
510
            upload_cb = self.arguments['progress_bar'].get_generator('Overwritting blocks')
511
            self.client.overwrite_object(object=self.path, start=start, end=end,
512
                source_file=f, upload_cb = upload_cb)
513
        except ClientError as err:
514
            raiseCLIError(err)
515

    
516
@command()
517
class store_manifest(_store_container_command):
518
    """Create a remote file with uploaded parts by manifestation"""
519

    
520
    def __init__(self, arguments={}):
521
        super(self.__class__, self).__init__(arguments)
522
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
523
        self.arguments['content_encoding']=ValueArgument('provide the object MIME content type',
524
            '--content-encoding')
525
        self.arguments['content_disposition'] = ValueArgument('provide the presentation style of the object',
526
            '--content-disposition')
527
        self.arguments['content_type']=ValueArgument('create object with specific content type',
528
            '--content-type')
529
        self.arguments['sharing']=SharingArgument(parsed_name='--sharing',
530
            help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
531
        self.arguments['public']=FlagArgument('make object publicly accessible', '--public')
532
        
533
    def main(self, container___path):
534
        super(self.__class__, self).main(container___path, path_is_optional=False)
535
        try:
536
            self.client.create_object_by_manifestation(self.path,
537
                content_encoding=self.get_argument('content_encoding'),
538
                content_disposition=self.get_argument('content_disposition'),
539
                content_type=self.get_argument('content_type'),
540
                sharing=self.get_argument('sharing'), public=self.get_argument('public'))
541
        except ClientError as err:
542
            raiseCLIError(err)
543

    
544
@command()
545
class store_upload(_store_container_command):
546
    """Upload a file"""
547

    
548
    def __init__(self, arguments={}):
549
        super(self.__class__, self).__init__(arguments)
550
        self.arguments['use_hashes'] = FlagArgument('provide hashmap file instead of data',
551
            '--use-hashes')
552
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
553
        self.arguments['unchunked'] = FlagArgument('avoid chunked transfer mode', '--unchunked')
554
        self.arguments['content_encoding']=ValueArgument('provide the object MIME content type',
555
            '--content-encoding')
556
        self.arguments['content_disposition'] = ValueArgument('provide the presentation style of the object',
557
            '--content-disposition')
558
        self.arguments['content_type']=ValueArgument('create object with specific content type',
559
            '--content-type')
560
        self.arguments['sharing']=SharingArgument(parsed_name='--sharing',
561
            help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
562
        self.arguments['public']=FlagArgument('make object publicly accessible', '--public')
563
        self.arguments['poolsize']=IntArgument('set pool size', '--with-pool-size')
564
        self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar', '--no-progress-bar')
565

    
566
    def main(self, local_path, container____path__):
567
        super(self.__class__, self).main(container____path__)
568
        remote_path = local_path if self.path is None else self.path
569
        poolsize = self.get_argument('poolsize')
570
        if poolsize is not None:
571
            self.POOL_SIZE = poolsize
572
        try:
573
            with open(local_path) as f:
574
                if self.get_argument('unchunked'):
575
                    self.client.upload_object_unchunked(remote_path, f,
576
                    etag=self.get_argument('etag'), withHashFile=self.get_argument('use_hashes'),
577
                    content_encoding=self.get_argument('content_encoding'),
578
                    content_disposition=self.get_argument('content_disposition'),
579
                    content_type=self.get_argument('content_type'),
580
                    sharing=self.get_argument('sharing'), public=self.get_argument('public'))
581
                else:
582
                    hash_cb=self.arguments['progress_bar'].get_generator('Calculating block hashes')
583
                    upload_cb=self.arguments['progress_bar'].get_generator('Uploading')
584
                    self.client.upload_object(remote_path, f, hash_cb=hash_cb, upload_cb=upload_cb,
585
                    content_encoding=self.get_argument('content_encoding'),
586
                    content_disposition=self.get_argument('content_disposition'),
587
                    content_type=self.get_argument('content_type'),
588
                    sharing=self.get_argument('sharing'), public=self.get_argument('public'))
589
        except ClientError as err:
590
            raiseCLIError(err)
591
        print 'Upload completed'
592

    
593
@command()
594
class store_download(_store_container_command):
595
    """Download a file"""
596

    
597
    def __init__(self, arguments={}):
598
        super(self.__class__, self).__init__(arguments)
599
        self.arguments['resume'] = FlagArgument(parsed_name='--resume',
600
            help = 'Resume a previous download instead of overwritting it')
601
        self.arguments['range'] = RangeArgument('show range of data', '--range')
602
        self.arguments['if_match'] = ValueArgument('show output if ETags match', '--if-match')
603
        self.arguments['if_none_match'] = ValueArgument('show output if ETags match',
604
            '--if-none-match')
605
        self.arguments['if_modified_since'] = DateArgument('show output modified since then',
606
            '--if-modified-since')
607
        self.arguments['if_unmodified_since'] = DateArgument('show output unmodified since then',
608
            '--if-unmodified-since')
609
        self.arguments['object_version'] = ValueArgument('get the specific version',
610
            '--object-version')
611
        self.arguments['poolsize']=IntArgument('set pool size', '--with-pool-size')
612
        self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
613
            '--no-progress-bar')
614

    
615
    def main(self, container___path, local_path=None):
616
        super(self.__class__, self).main(container___path, path_is_optional=False)
617

    
618
        #setup output stream
619
        parallel = False
620
        if local_path is None:
621
            out = stdout
622
        else:
623
            try:
624
                if self.get_argument('resume'):
625
                    out=open(local_path, 'rwb+')
626
                else:
627
                    out=open(local_path, 'wb+')
628
            except IOError as err:
629
                raise CLIError(message='Cannot write to file %s - %s'%(local_path,unicode(err)),
630
                    importance=1)
631
        download_cb = self.arguments['progress_bar'].get_generator('Downloading')
632
        poolsize = self.get_argument('poolsize')
633
        if poolsize is not None:
634
            self.POOL_SIZE = int(poolsize)
635

    
636
        try:
637
            self.client.download_object(self.path, out, download_cb,
638
                range=self.get_argument('range'), version=self.get_argument('object_version'),
639
                if_match=self.get_argument('if_match'), resume=self.get_argument('resume'),
640
                if_none_match=self.get_argument('if_none_match'),
641
                if_modified_since=self.get_argument('if_modified_since'),
642
                if_unmodified_since=self.get_argument('if_unmodified_since'))
643
        except ClientError as err:
644
            raiseCLIError(err)
645
        except KeyboardInterrupt:
646
            print('\ndownload canceled by user')
647
            if local_path is not None:
648
                print('to resume, re-run with --resume')
649
        print
650

    
651
@command()
652
class store_hashmap(_store_container_command):
653
    """Get the hashmap of an object"""
654

    
655
    def __init__(self, arguments={}):
656
        super(self.__class__, self).__init__(arguments)
657
        self.arguments['if_match'] = ValueArgument('show output if ETags match', '--if-match')
658
        self.arguments['if_none_match'] = ValueArgument('show output if ETags match',
659
            '--if-none-match')
660
        self.arguments['if_modified_since'] = DateArgument('show output modified since then',
661
            '--if-modified-since')
662
        self.arguments['if_unmodified_since'] = DateArgument('show output unmodified since then',
663
            '--if-unmodified-since')
664
        self.arguments['object_version'] = ValueArgument('get the specific version',
665
            '--object-version')
666

    
667
    def main(self, container___path):
668
        super(self.__class__, self).main(container___path, path_is_optional=False)
669
        try:
670
            data = self.client.get_object_hashmap(self.path,
671
                version=self.arguments('object_version'),
672
                if_match=self.arguments('if_match'),
673
                if_none_match=self.arguments('if_none_match'),
674
                if_modified_since=self.arguments('if_modified_since'),
675
                if_unmodified_since=self.arguments('if_unmodified_since'))
676
        except ClientError as err:
677
            raiseCLIError(err)
678
        print_dict(data)
679

    
680
@command()
681
class store_delete(_store_container_command):
682
    """Delete a container [or an object]"""
683

    
684
    def __init__(self, arguments={}):
685
        super(self.__class__, self).__init__(arguments)
686
        self.arguments['until'] = DateArgument('remove history until that date', '--until')
687
        self.arguments['recursive'] = FlagArgument('empty dir or container and delete (if dir)',
688
            '--recursive')
689
        self.arguments['delimiter'] = DelimiterArgument(self, parsed_name='--delimiter',
690
            help = 'mass delete objects with path staring with <object><delimiter>')
691

    
692
    def main(self, container____path__):
693
        super(self.__class__, self).main(container____path__)
694
        try:
695
            if self.path is None:
696
                self.client.del_container(until=self.get_argument('until'),
697
                    delimiter=self.get_argument('delimiter'))
698
            else:
699
                #self.client.delete_object(self.path)
700
                self.client.del_object(self.path, until=self.get_argument('until'),
701
                    delimiter=self.get_argument('delimiter'))
702
        except ClientError as err:
703
            raiseCLIError(err)
704

    
705
@command()
706
class store_purge(_store_container_command):
707
    """Purge a container"""
708
    
709
    def main(self, container):
710
        super(self.__class__, self).main(container)
711
        try:
712
            self.client.purge_container()
713
        except ClientError as err:
714
            raiseCLIError(err)
715

    
716
@command()
717
class store_publish(_store_container_command):
718
    """Publish an object"""
719

    
720
    def main(self, container___path):
721
        super(self.__class__, self).main(container___path, path_is_optional=False)
722
        try:
723
            self.client.publish_object(self.path)
724
        except ClientError as err:
725
            raiseCLIError(err)
726

    
727
@command()
728
class store_unpublish(_store_container_command):
729
    """Unpublish an object"""
730

    
731
    def main(self, container___path):
732
        super(self.__class__, self).main(container___path, path_is_optional=False)
733
        try:
734
            self.client.unpublish_object(self.path)
735
        except ClientError as err:
736
            raiseCLIError(err)
737

    
738
@command()
739
class store_permitions(_store_container_command):
740
    """Get object read/write permitions"""
741

    
742
    def main(self, container___path):
743
        super(self.__class__, self).main(container___path, path_is_optional=False)
744
        try:
745
            reply = self.client.get_object_sharing(self.path)
746
            print_dict(reply)
747
        except ClientError as err:
748
            raiseCLIError(err)
749

    
750
@command()
751
class store_setpermitions(_store_container_command):
752
    """Set sharing permitions"""
753

    
754
    def format_permition_dict(self,permitions):
755
        read = False
756
        write = False
757
        for perms in permitions:
758
            splstr = perms.split('=')
759
            if 'read' == splstr[0]:
760
                read = [user_or_group.strip() \
761
                for user_or_group in splstr[1].split(',')]
762
            elif 'write' == splstr[0]:
763
                write = [user_or_group.strip() \
764
                for user_or_group in splstr[1].split(',')]
765
            else:
766
                read = False
767
                write = False
768
        if not read and not write:
769
            raise CLIError(message='Usage:\tread=<groups,users> write=<groups,users>',
770
                importance=0)
771
        return (read,write)
772

    
773
    def main(self, container___path, *permitions):
774
        super(self.__class__, self).main(container___path, path_is_optional=False)
775
        (read, write) = self.format_permition_dict(permitions)
776
        try:
777
            self.client.set_object_sharing(self.path,
778
                read_permition=read, write_permition=write)
779
        except ClientError as err:
780
            raiseCLIError(err)
781

    
782
@command()
783
class store_delpermitions(_store_container_command):
784
    """Delete all sharing permitions"""
785

    
786
    def main(self, container___path):
787
        super(self.__class__, self).main(container___path, path_is_optional=False)
788
        try:
789
            self.client.del_object_sharing(self.path)
790
        except ClientError as err:
791
            raiseCLIError(err)
792

    
793
@command()
794
class store_info(_store_container_command):
795
    """Get information for account [, container [or object]]"""
796

    
797
    
798
    def main(self, container____path__=None):
799
        super(self.__class__, self).main(container____path__)
800
        try:
801
            if self.container is None:
802
                reply = self.client.get_account_info()
803
            elif self.path is None:
804
                reply = self.client.get_container_info(self.container)
805
            else:
806
                reply = self.client.get_object_info(self.path)
807
        except ClientError as err:
808
            raiseCLIError(err)
809
        print_dict(reply)
810

    
811
@command()
812
class store_meta(_store_container_command):
813
    """Get custom meta-content for account [, container [or object]]"""
814

    
815
    def __init__(self, arguments = {}):
816
        super(self.__class__, self).__init__(arguments)
817
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
818
        self.arguments['until'] = DateArgument('show metadata until then', '--until')
819
        self.arguments['object_version'] = ValueArgument(parsed_name='--object-version',
820
            help='show specific version \ (applies only for objects)')
821

    
822
    def main(self, container____path__ = None):
823
        super(self.__class__, self).main(container____path__)
824

    
825
        detail = self.get_argument('detail')
826
        try:
827
            if self.container is None:
828
                print(bold(self.client.account))
829
                if detail:
830
                    reply = self.client.get_account_info(until=self.get_argument('until'))
831
                else:
832
                    reply = self.client.get_account_meta(until=self.get_argument('until'))
833
                    reply = pretty_keys(reply, '-')
834
            elif self.path is None:
835
                print(bold(self.client.account+': '+self.container))
836
                if detail:
837
                    reply = self.client.get_container_info(until = self.get_argument('until'))
838
                else:
839
                    cmeta = self.client.get_container_meta(until=self.get_argument('until'))
840
                    ometa = self.client.get_container_object_meta(until=self.get_argument('until'))
841
                    reply = {'container-meta':pretty_keys(cmeta, '-'),
842
                        'object-meta':pretty_keys(ometa, '-')}
843
            else:
844
                print(bold(self.client.account+': '+self.container+':'+self.path))
845
                version=self.get_argument('object_version')
846
                if detail:
847
                    reply = self.client.get_object_info(self.path, version = version)
848
                else:
849
                    reply = self.client.get_object_meta(self.path, version=version)
850
                    reply = pretty_keys(pretty_keys(reply, '-'))
851
        except ClientError as err:
852
            raiseCLIError(err)
853
        print_dict(reply)
854

    
855
@command()
856
class store_setmeta(_store_container_command):
857
    """Set a new metadatum for account [, container [or object]]"""
858

    
859
    def main(self, metakey___metaval, container____path__=None):
860
        super(self.__class__, self).main(container____path__)
861
        try:
862
            metakey, metavalue = metakey___metaval.split(':')
863
        except ValueError:
864
            raise CLIError(message='Meta variables should be formated as metakey:metavalue',
865
                importance=1)
866
        try:
867
            if self.container is None:
868
                self.client.set_account_meta({metakey:metavalue})
869
            elif self.path is None:
870
                self.client.set_container_meta({metakey:metavalue})
871
            else:
872
                self.client.set_object_meta(self.path, {metakey:metavalue})
873
        except ClientError as err:
874
            raiseCLIError(err)
875

    
876
@command()
877
class store_delmeta(_store_container_command):
878
    """Delete an existing metadatum of account [, container [or object]]"""
879

    
880
    def main(self, metakey, container____path__=None):
881
        super(self.__class__, self).main(container____path__)
882
        try:
883
            if self.container is None:
884
                self.client.del_account_meta(metakey)
885
            elif self.path is None:
886
                self.client.del_container_meta(metakey)
887
            else:
888
                self.client.del_object_meta(metakey, self.path)
889
        except ClientError as err:
890
            raiseCLIError(err)
891

    
892
@command()
893
class store_quota(_store_account_command):
894
    """Get  quota for account [or container]"""
895

    
896
    def main(self, container = None):
897
        super(self.__class__, self).main()
898
        try:
899
            if container is None:
900
                reply = self.client.get_account_quota()
901
            else:
902
                reply = self.client.get_container_quota(container)
903
        except ClientError as err:
904
            raiseCLIError(err)
905
        print_dict(reply)
906

    
907
@command()
908
class store_setquota(_store_account_command):
909
    """Set new quota (in KB) for account [or container]"""
910

    
911
    def main(self, quota, container = None):
912
        super(self.__class__, self).main()
913
        try:
914
            if container is None:
915
                self.client.set_account_quota(quota)
916
            else:
917
                self.client.container = container
918
                self.client.set_container_quota(quota)
919
        except ClientError as err:
920
            raiseCLIError(err)
921

    
922
@command()
923
class store_versioning(_store_account_command):
924
    """Get  versioning for account [or container ]"""
925

    
926
    def main(self, container = None):
927
        super(self.__class__, self).main()
928
        try:
929
            if container is None:
930
                reply = self.client.get_account_versioning()
931
            else:
932
                reply = self.client.get_container_versioning(container)
933
        except ClientError as err:
934
            raiseCLIError(err)
935
        print_dict(reply)
936

    
937
@command()
938
class store_setversioning(_store_account_command):
939
    """Set new versioning (auto, none) for account [or container]"""
940

    
941
    def main(self, versioning, container = None):
942
        super(self.__class__, self).main()
943
        try:
944
            if container is None:
945
                self.client.set_account_versioning(versioning)
946
            else:
947
                self.client.container = container
948
                self.client.set_container_versioning(versioning)
949
        except ClientError as err:
950
            raiseCLIError(err)
951

    
952
@command()
953
class store_group(_store_account_command):
954
    """Get user groups details for account"""
955

    
956
    def main(self):
957
        super(self.__class__, self).main()
958
        try:
959
            reply = self.client.get_account_group()
960
        except ClientError as err:
961
            raiseCLIError(err)
962
        print_dict(reply)
963

    
964
@command()
965
class store_setgroup(_store_account_command):
966
    """Create/update a new user group on account"""
967

    
968
    def main(self, groupname, *users):
969
        super(self.__class__, self).main()
970
        try:
971
            self.client.set_account_group(groupname, users)
972
        except ClientError as err:
973
            raiseCLIError(err)
974

    
975
@command()
976
class store_delgroup(_store_account_command):
977
    """Delete a user group on an account"""
978

    
979
    def main(self, groupname):
980
        super(self.__class__, self).main()
981
        try:
982
            self.client.del_account_group(groupname)
983
        except ClientError as err:
984
            raiseCLIError(err)
985

    
986
@command()
987
class store_sharers(_store_account_command):
988
    """List the accounts that share objects with default account"""
989

    
990
    def __init__(self, arguments = {}):
991
        super(self.__class__, self).__init__(arguments)
992
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
993
        self.arguments['limit'] = IntArgument('show limited output', '--n', default=1000)
994
        self.arguments['marker'] = ValueArgument('show output greater then marker', '--marker')
995

    
996
    def main(self):
997
        super(self.__class__, self).main()
998
        try:
999
            accounts = self.client.get_sharing_accounts(marker=self.get_argument('marker'))
1000
        except ClientError as err:
1001
            raiseCLIError(err)
1002

    
1003
        for acc in accounts:
1004
            stdout.write(bold(acc['name'])+' ')
1005
            if self.get_argument('detail'):
1006
                print_dict(acc, exclude='name', ident=18)
1007
        if not self.get_argument('detail'):
1008
            print
1009

    
1010
@command()
1011
class store_versions(_store_container_command):
1012
    """Get the version list of an object"""
1013

    
1014
    def main(self, container___path):
1015
        super(store_versions, self).main(container___path)
1016
        try:
1017
            versions = self.client.get_object_versionlist(self.path)
1018
        except ClientError as err:
1019
            raise CLIError(err)
1020

    
1021
        print('%s:%s versions'%(self.container,self.path))
1022
        for vitem in versions:
1023
            t = localtime(float(vitem[1]))
1024
            vid = bold(unicode(vitem[0]))
1025
            print('\t%s \t(%s)'%(vid, strftime('%d-%m-%Y %H:%M:%S', t)))