Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 5eae854d

History | View | Annotate | Download (42.1 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.command
33

    
34
from kamaki.cli import command#, 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
from . import _command_init
40
#set_api_description('store', 'Pithos+ storage commands')
41
API_DESCRIPTION = dict(store='Pithos+ storage commands')
42
from kamaki.clients.pithos import PithosClient, ClientError
43
from colors import bold
44
from sys import stdout, exit
45
import signal
46
from time import localtime, strftime, strptime, mktime
47
from datetime import datetime as dtm
48

    
49
from progress.bar import IncrementalBar
50

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

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

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

    
82
class ProgressBarArgument(FlagArgument):
83

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

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

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

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

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

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

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

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

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

    
175
#Command specs
176
class _pithos_init(_command_init):
177
    def main(self):
178
        self.token = self.config.get('store', 'token') or self.config.get('global', 'token')
179
        self.base_url = self.config.get('store', 'url') or self.config.get('global', 'url')
180
        self.account = self.config.get('store', 'account') or self.config.get('global', 'account')
181
        self.container = self.config.get('store', 'container') or self.config.get('global',
182
            'container')
183
        self.client = PithosClient(base_url=self.base_url, token=self.token, account=self.account,
184
            container=self.container)
185

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

    
189
    def __init__(self, arguments={}):
190
        super(_store_account_command, self).__init__(arguments)
191
        self.arguments['account'] = 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
class _store_container_command(_store_account_command):
202
    """Base class for container level storage commands"""
203

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

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

    
234
    def main(self, container_with_path=None, path_is_optional=True):
235
        super(_store_container_command, self).main()
236
        if container_with_path is not None:
237
            self.extract_container_and_path(container_with_path, path_is_optional)
238
            self.client.container = self.container
239
        elif self.get_argument('container') is not None:
240
            self.client.container = self.get_argument('container')
241
        self.container = self.client.container
242

    
243
"""
244
@command()
245
class store_test(_store_container_command):
246
    "Test stuff something""
247

248
    def main(self):
249
        super(self.__class__, self).main('pithos')
250
        r = self.client.container_get()
251
        print(unicode(r.content)+' '+unicode(r.json))
252
"""
253

    
254
@command()
255
class store_list(_store_container_command):
256
    """List containers, object trees or objects in a directory
257
    """
258

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

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

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

    
341
    def main(self, container____path__=None):
342
        super(self.__class__, self).main(container____path__)
343
        try:
344
            if self.container is None:
345
                r = self.client.account_get(limit=self.get_argument('limit'),
346
                    marker=self.get_argument('marker'),
347
                    if_modified_since=self.get_argument('if_modified_since'),
348
                    if_unmodified_since=self.get_argument('if_unmodified_since'),
349
                    until=self.get_argument('until'),
350
                    show_only_shared=self.get_argument('shared'))
351
                self.print_containers(r.json)
352
            else:
353
                r = self.client.container_get(limit=self.get_argument('limit'),
354
                    marker=self.get_argument('marker'), prefix=self.get_argument('prefix'),
355
                    delimiter=self.get_argument('delimiter'), path=self.get_argument('path'),
356
                    if_modified_since=self.get_argument('if_modified_since'),
357
                    if_unmodified_since=self.get_argument('if_unmodified_since'),
358
                    until=self.get_argument('until'),
359
                    meta=self.get_argument('meta'), show_only_shared=self.get_argument('shared'))
360
                self.print_objects(r.json)
361
        except ClientError as err:
362
            raiseCLIError(err)
363

    
364
@command()
365
class store_mkdir(_store_container_command):
366
    """Create a directory"""
367

    
368
    def main(self, container___directory):
369
        super(self.__class__, self).main(container___directory, path_is_optional=False)
370
        try:
371
            self.client.create_directory(self.path)
372
        except ClientError as err:
373
            raiseCLIError(err)
374

    
375
@command()
376
class store_create(_store_container_command):
377
    """Create a container or a directory object"""
378

    
379

    
380
    def __init__(self, arguments={}):
381
        super(self.__class__, self).__init__(arguments)
382
        self.arguments['versioning'] = ValueArgument('set container versioning (auto/none)',
383
            '--versioning')
384
        self.arguments['quota'] = IntArgument('set default container quota', '--quota')
385
        self.arguments['meta'] = MetaArgument('set container metadata', '---meta')
386

    
387
    def main(self, container____directory__):
388
        super(self.__class__, self).main(container____directory__)
389
        try:
390
            if self.path is None:
391
                self.client.container_put(quota=self.get_argument('quota'),
392
                    versioning=self.get_argument('versioning'),
393
                    metadata=self.get_argument('meta'))
394
            else:
395
                self.client.create_directory(self.path)
396
        except ClientError as err:
397
            raiseCLIError(err)
398

    
399
@command()
400
class store_copy(_store_container_command):
401
    """Copy an object"""
402

    
403
    def __init__(self, arguments={}):
404
        super(self.__class__, self).__init__(arguments)
405
        self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
406
        self.arguments['public']=ValueArgument('make object publicly accessible', '--public')
407
        self.arguments['content_type']=ValueArgument('change object\'s content type',
408
            '--content-type')
409
        self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
410
            help = u'mass copy objects with path staring with src_object + delimiter')
411
        self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
412

    
413
    def main(self, source_container___path, destination_container____path__):
414
        super(self.__class__, self).main(source_container___path, path_is_optional=False)
415
        try:
416
            dst = destination_container____path__.split(':')
417
            dst_cont = dst[0]
418
            dst_path = dst[1] if len(dst) > 1 else False
419
            self.client.copy_object(src_container = self.container, src_object = self.path,
420
                dst_container = dst_cont, dst_object = dst_path,
421
                source_version=self.get_argument('source_version'),
422
                public=self.get_argument('public'),
423
                content_type=self.get_argument('content_type'),
424
                delimiter=self.get_argument('delimiter'))
425
        except ClientError as err:
426
            raiseCLIError(err)
427

    
428
@command()
429
class store_move(_store_container_command):
430
    """Copy an object"""
431

    
432
    def __init__(self, arguments={}):
433
        super(self.__class__, self).__init__(arguments)
434

    
435
        self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
436
        self.arguments['public']=FlagArgument('make object publicly accessible', '--public')
437
        self.arguments['content_type']=ValueArgument('change object\'s content type',
438
            '--content-type')
439
        self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
440
            help = u'mass copy objects with path staring with src_object + delimiter')
441
        self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
442

    
443
    def main(self, source_container___path, destination_container____path__):
444
        super(self.__class__, self).main(source_container___path, path_is_optional=False)
445
        try:
446
            dst = destination_container____path__.split(':')
447
            dst_cont = dst[0]
448
            dst_path = dst[1] if len(dst) > 1 else False
449
            self.client.move_object(src_container = self.container, src_object = self.path,
450
                dst_container = dst_cont, dst_object = dst_path,
451
                source_version=self.get_argument('source_version'),
452
                public=self.get_argument('public'),
453
                content_type=self.get_argument('content_type'),
454
                delimiter=self.get_argument('delimiter'))
455
        except ClientError as err:
456
            raiseCLIError(err)
457

    
458
@command()
459
class store_append(_store_container_command):
460
    """Append local file to (existing) remote object"""
461

    
462
    def __init__(self, arguments={}):
463
        super(self.__class__, self).__init__(arguments)
464
        self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar', '--no-progress-bar')
465

    
466
    def main(self, local_path, container___path):
467
        super(self.__class__, self).main(container___path, path_is_optional=False)
468
        try:
469
            f = open(local_path, 'r')
470
            upload_cb = self.arguments['progress_bar'].get_generator('Appending blocks')
471
            self.client.append_object(object=self.path, source_file = f, upload_cb = upload_cb)
472
        except ClientError as err:
473
            raiseCLIError(err)
474

    
475
@command()
476
class store_truncate(_store_container_command):
477
    """Truncate remote file up to a size"""
478

    
479
    def main(self, container___path, size=0):
480
        super(self.__class__, self).main(container___path, path_is_optional=False)
481
        try:
482
            self.client.truncate_object(self.path, size)
483
        except ClientError as err:
484
            raiseCLIError(err)
485

    
486
@command()
487
class store_overwrite(_store_container_command):
488
    """Overwrite part (from start to end) of a remote file"""
489

    
490
    def __init__(self, arguments={}):
491
        super(self.__class__, self).__init__(arguments)
492
        self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
493
            '--no-progress-bar')
494

    
495
    def main(self, local_path, container___path, start, end):
496
        super(self.__class__, self).main(container___path, path_is_optional=False)
497
        try:
498
            f = open(local_path, 'r')
499
            upload_cb = self.arguments['progress_bar'].get_generator('Overwritting blocks')
500
            self.client.overwrite_object(object=self.path, start=start, end=end,
501
                source_file=f, upload_cb = upload_cb)
502
        except ClientError as err:
503
            raiseCLIError(err)
504

    
505
@command()
506
class store_manifest(_store_container_command):
507
    """Create a remote file with uploaded parts by manifestation"""
508

    
509
    def __init__(self, arguments={}):
510
        super(self.__class__, self).__init__(arguments)
511
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
512
        self.arguments['content_encoding']=ValueArgument('provide the object MIME content type',
513
            '--content-encoding')
514
        self.arguments['content_disposition'] = ValueArgument('provide the presentation style of the object',
515
            '--content-disposition')
516
        self.arguments['content_type']=ValueArgument('create object with specific content type',
517
            '--content-type')
518
        self.arguments['sharing']=SharingArgument(parsed_name='--sharing',
519
            help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
520
        self.arguments['public']=FlagArgument('make object publicly accessible', '--public')
521
        
522
    def main(self, container___path):
523
        super(self.__class__, self).main(container___path, path_is_optional=False)
524
        try:
525
            self.client.create_object_by_manifestation(self.path,
526
                content_encoding=self.get_argument('content_encoding'),
527
                content_disposition=self.get_argument('content_disposition'),
528
                content_type=self.get_argument('content_type'),
529
                sharing=self.get_argument('sharing'), public=self.get_argument('public'))
530
        except ClientError as err:
531
            raiseCLIError(err)
532

    
533
@command()
534
class store_upload(_store_container_command):
535
    """Upload a file"""
536

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

    
555
    def main(self, local_path, container____path__):
556
        super(self.__class__, self).main(container____path__)
557
        remote_path = local_path if self.path is None else self.path
558
        poolsize = self.get_argument('poolsize')
559
        if poolsize is not None:
560
            self.POOL_SIZE = poolsize
561
        try:
562
            with open(local_path) as f:
563
                if self.get_argument('unchunked'):
564
                    self.client.upload_object_unchunked(remote_path, f,
565
                    etag=self.get_argument('etag'), withHashFile=self.get_argument('use_hashes'),
566
                    content_encoding=self.get_argument('content_encoding'),
567
                    content_disposition=self.get_argument('content_disposition'),
568
                    content_type=self.get_argument('content_type'),
569
                    sharing=self.get_argument('sharing'), public=self.get_argument('public'))
570
                else:
571
                    hash_cb=self.arguments['progress_bar'].get_generator('Calculating block hashes')
572
                    upload_cb=self.arguments['progress_bar'].get_generator('Uploading')
573
                    self.client.upload_object(remote_path, f, hash_cb=hash_cb, upload_cb=upload_cb,
574
                    content_encoding=self.get_argument('content_encoding'),
575
                    content_disposition=self.get_argument('content_disposition'),
576
                    content_type=self.get_argument('content_type'),
577
                    sharing=self.get_argument('sharing'), public=self.get_argument('public'))
578
        except ClientError as err:
579
            raiseCLIError(err)
580
        print 'Upload completed'
581

    
582
@command()
583
class store_download(_store_container_command):
584
    """Download a file"""
585

    
586
    def __init__(self, arguments={}):
587
        super(self.__class__, self).__init__(arguments)
588
        self.arguments['resume'] = FlagArgument(parsed_name='--resume',
589
            help = 'Resume a previous download instead of overwritting it')
590
        self.arguments['range'] = RangeArgument('show range of data', '--range')
591
        self.arguments['if_match'] = ValueArgument('show output if ETags match', '--if-match')
592
        self.arguments['if_none_match'] = ValueArgument('show output if ETags match',
593
            '--if-none-match')
594
        self.arguments['if_modified_since'] = DateArgument('show output modified since then',
595
            '--if-modified-since')
596
        self.arguments['if_unmodified_since'] = DateArgument('show output unmodified since then',
597
            '--if-unmodified-since')
598
        self.arguments['object_version'] = ValueArgument('get the specific version',
599
            '--object-version')
600
        self.arguments['poolsize']=IntArgument('set pool size', '--with-pool-size')
601
        self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
602
            '--no-progress-bar')
603

    
604
    def main(self, container___path, local_path=None):
605
        super(self.__class__, self).main(container___path, path_is_optional=False)
606

    
607
        #setup output stream
608
        parallel = False
609
        if local_path is None:
610
            out = stdout
611
        else:
612
            try:
613
                if self.get_argument('resume'):
614
                    out=open(local_path, 'rwb+')
615
                else:
616
                    out=open(local_path, 'wb+')
617
            except IOError as err:
618
                raise CLIError(message='Cannot write to file %s - %s'%(local_path,unicode(err)),
619
                    importance=1)
620
        download_cb = self.arguments['progress_bar'].get_generator('Downloading')
621
        poolsize = self.get_argument('poolsize')
622
        if poolsize is not None:
623
            self.POOL_SIZE = int(poolsize)
624

    
625
        try:
626
            self.client.download_object(self.path, out, download_cb,
627
                range=self.get_argument('range'), version=self.get_argument('object_version'),
628
                if_match=self.get_argument('if_match'), resume=self.get_argument('resume'),
629
                if_none_match=self.get_argument('if_none_match'),
630
                if_modified_since=self.get_argument('if_modified_since'),
631
                if_unmodified_since=self.get_argument('if_unmodified_since'))
632
        except ClientError as err:
633
            raiseCLIError(err)
634
        except KeyboardInterrupt:
635
            print('\ndownload canceled by user')
636
            if local_path is not None:
637
                print('to resume, re-run with --resume')
638
        print
639

    
640
@command()
641
class store_hashmap(_store_container_command):
642
    """Get the hashmap of an object"""
643

    
644
    def __init__(self, arguments={}):
645
        super(self.__class__, self).__init__(arguments)
646
        self.arguments['if_match'] = ValueArgument('show output if ETags match', '--if-match')
647
        self.arguments['if_none_match'] = ValueArgument('show output if ETags match',
648
            '--if-none-match')
649
        self.arguments['if_modified_since'] = DateArgument('show output modified since then',
650
            '--if-modified-since')
651
        self.arguments['if_unmodified_since'] = DateArgument('show output unmodified since then',
652
            '--if-unmodified-since')
653
        self.arguments['object_version'] = ValueArgument('get the specific version',
654
            '--object-version')
655

    
656
    def main(self, container___path):
657
        super(self.__class__, self).main(container___path, path_is_optional=False)
658
        try:
659
            data = self.client.get_object_hashmap(self.path,
660
                version=self.arguments('object_version'),
661
                if_match=self.arguments('if_match'),
662
                if_none_match=self.arguments('if_none_match'),
663
                if_modified_since=self.arguments('if_modified_since'),
664
                if_unmodified_since=self.arguments('if_unmodified_since'))
665
        except ClientError as err:
666
            raiseCLIError(err)
667
        print_dict(data)
668

    
669
@command()
670
class store_delete(_store_container_command):
671
    """Delete a container [or an object]"""
672

    
673
    def __init__(self, arguments={}):
674
        super(self.__class__, self).__init__(arguments)
675
        self.arguments['until'] = DateArgument('remove history until that date', '--until')
676
        self.arguments['recursive'] = FlagArgument('empty dir or container and delete (if dir)',
677
            '--recursive')
678
        self.arguments['delimiter'] = DelimiterArgument(self, parsed_name='--delimiter',
679
            help = 'mass delete objects with path staring with <object><delimiter>')
680

    
681
    def main(self, container____path__):
682
        super(self.__class__, self).main(container____path__)
683
        try:
684
            if self.path is None:
685
                self.client.del_container(until=self.get_argument('until'),
686
                    delimiter=self.get_argument('delimiter'))
687
            else:
688
                #self.client.delete_object(self.path)
689
                self.client.del_object(self.path, until=self.get_argument('until'),
690
                    delimiter=self.get_argument('delimiter'))
691
        except ClientError as err:
692
            raiseCLIError(err)
693

    
694
@command()
695
class store_purge(_store_container_command):
696
    """Purge a container"""
697
    
698
    def main(self, container):
699
        super(self.__class__, self).main(container)
700
        try:
701
            self.client.purge_container()
702
        except ClientError as err:
703
            raiseCLIError(err)
704

    
705
@command()
706
class store_publish(_store_container_command):
707
    """Publish an object"""
708

    
709
    def main(self, container___path):
710
        super(self.__class__, self).main(container___path, path_is_optional=False)
711
        try:
712
            self.client.publish_object(self.path)
713
        except ClientError as err:
714
            raiseCLIError(err)
715

    
716
@command()
717
class store_unpublish(_store_container_command):
718
    """Unpublish 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.unpublish_object(self.path)
724
        except ClientError as err:
725
            raiseCLIError(err)
726

    
727
@command()
728
class store_permitions(_store_container_command):
729
    """Get object read/write permitions"""
730

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

    
739
@command()
740
class store_setpermitions(_store_container_command):
741
    """Set sharing permitions"""
742

    
743
    def format_permition_dict(self,permitions):
744
        read = False
745
        write = False
746
        for perms in permitions:
747
            splstr = perms.split('=')
748
            if 'read' == splstr[0]:
749
                read = [user_or_group.strip() \
750
                for user_or_group in splstr[1].split(',')]
751
            elif 'write' == splstr[0]:
752
                write = [user_or_group.strip() \
753
                for user_or_group in splstr[1].split(',')]
754
            else:
755
                read = False
756
                write = False
757
        if not read and not write:
758
            raise CLIError(message='Usage:\tread=<groups,users> write=<groups,users>',
759
                importance=0)
760
        return (read,write)
761

    
762
    def main(self, container___path, *permitions):
763
        super(self.__class__, self).main(container___path, path_is_optional=False)
764
        (read, write) = self.format_permition_dict(permitions)
765
        try:
766
            self.client.set_object_sharing(self.path,
767
                read_permition=read, write_permition=write)
768
        except ClientError as err:
769
            raiseCLIError(err)
770

    
771
@command()
772
class store_delpermitions(_store_container_command):
773
    """Delete all sharing permitions"""
774

    
775
    def main(self, container___path):
776
        super(self.__class__, self).main(container___path, path_is_optional=False)
777
        try:
778
            self.client.del_object_sharing(self.path)
779
        except ClientError as err:
780
            raiseCLIError(err)
781

    
782
@command()
783
class store_info(_store_container_command):
784
    """Get information for account [, container [or object]]"""
785

    
786
    
787
    def main(self, container____path__=None):
788
        super(self.__class__, self).main(container____path__)
789
        try:
790
            if self.container is None:
791
                reply = self.client.get_account_info()
792
            elif self.path is None:
793
                reply = self.client.get_container_info(self.container)
794
            else:
795
                reply = self.client.get_object_info(self.path)
796
        except ClientError as err:
797
            raiseCLIError(err)
798
        print_dict(reply)
799

    
800
@command()
801
class store_meta(_store_container_command):
802
    """Get custom meta-content for account [, container [or object]]"""
803

    
804
    def __init__(self, arguments = {}):
805
        super(self.__class__, self).__init__(arguments)
806
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
807
        self.arguments['until'] = DateArgument('show metadata until then', '--until')
808
        self.arguments['object_version'] = ValueArgument(parsed_name='--object-version',
809
            help='show specific version \ (applies only for objects)')
810

    
811
    def main(self, container____path__ = None):
812
        super(self.__class__, self).main(container____path__)
813

    
814
        detail = self.get_argument('detail')
815
        try:
816
            if self.container is None:
817
                print(bold(self.client.account))
818
                if detail:
819
                    reply = self.client.get_account_info(until=self.get_argument('until'))
820
                else:
821
                    reply = self.client.get_account_meta(until=self.get_argument('until'))
822
                    reply = pretty_keys(reply, '-')
823
            elif self.path is None:
824
                print(bold(self.client.account+': '+self.container))
825
                if detail:
826
                    reply = self.client.get_container_info(until = self.get_argument('until'))
827
                else:
828
                    cmeta = self.client.get_container_meta(until=self.get_argument('until'))
829
                    ometa = self.client.get_container_object_meta(until=self.get_argument('until'))
830
                    reply = {'container-meta':pretty_keys(cmeta, '-'),
831
                        'object-meta':pretty_keys(ometa, '-')}
832
            else:
833
                print(bold(self.client.account+': '+self.container+':'+self.path))
834
                version=self.get_argument('object_version')
835
                if detail:
836
                    reply = self.client.get_object_info(self.path, version = version)
837
                else:
838
                    reply = self.client.get_object_meta(self.path, version=version)
839
                    reply = pretty_keys(pretty_keys(reply, '-'))
840
        except ClientError as err:
841
            raiseCLIError(err)
842
        print_dict(reply)
843

    
844
@command()
845
class store_setmeta(_store_container_command):
846
    """Set a new metadatum for account [, container [or object]]"""
847

    
848
    def main(self, metakey___metaval, container____path__=None):
849
        super(self.__class__, self).main(container____path__)
850
        try:
851
            metakey, metavalue = metakey___metaval.split(':')
852
        except ValueError:
853
            raise CLIError(message='Meta variables should be formated as metakey:metavalue',
854
                importance=1)
855
        try:
856
            if self.container is None:
857
                self.client.set_account_meta({metakey:metavalue})
858
            elif self.path is None:
859
                self.client.set_container_meta({metakey:metavalue})
860
            else:
861
                self.client.set_object_meta(self.path, {metakey:metavalue})
862
        except ClientError as err:
863
            raiseCLIError(err)
864

    
865
@command()
866
class store_delmeta(_store_container_command):
867
    """Delete an existing metadatum of account [, container [or object]]"""
868

    
869
    def main(self, metakey, container____path__=None):
870
        super(self.__class__, self).main(container____path__)
871
        try:
872
            if self.container is None:
873
                self.client.del_account_meta(metakey)
874
            elif self.path is None:
875
                self.client.del_container_meta(metakey)
876
            else:
877
                self.client.del_object_meta(metakey, self.path)
878
        except ClientError as err:
879
            raiseCLIError(err)
880

    
881
@command()
882
class store_quota(_store_account_command):
883
    """Get  quota for account [or container]"""
884

    
885
    def main(self, container = None):
886
        super(self.__class__, self).main()
887
        try:
888
            if container is None:
889
                reply = self.client.get_account_quota()
890
            else:
891
                reply = self.client.get_container_quota(container)
892
        except ClientError as err:
893
            raiseCLIError(err)
894
        print_dict(reply)
895

    
896
@command()
897
class store_setquota(_store_account_command):
898
    """Set new quota (in KB) for account [or container]"""
899

    
900
    def main(self, quota, container = None):
901
        super(self.__class__, self).main()
902
        try:
903
            if container is None:
904
                self.client.set_account_quota(quota)
905
            else:
906
                self.client.container = container
907
                self.client.set_container_quota(quota)
908
        except ClientError as err:
909
            raiseCLIError(err)
910

    
911
@command()
912
class store_versioning(_store_account_command):
913
    """Get  versioning for account [or container ]"""
914

    
915
    def main(self, container = None):
916
        super(self.__class__, self).main()
917
        try:
918
            if container is None:
919
                reply = self.client.get_account_versioning()
920
            else:
921
                reply = self.client.get_container_versioning(container)
922
        except ClientError as err:
923
            raiseCLIError(err)
924
        print_dict(reply)
925

    
926
@command()
927
class store_setversioning(_store_account_command):
928
    """Set new versioning (auto, none) for account [or container]"""
929

    
930
    def main(self, versioning, container = None):
931
        super(self.__class__, self).main()
932
        try:
933
            if container is None:
934
                self.client.set_account_versioning(versioning)
935
            else:
936
                self.client.container = container
937
                self.client.set_container_versioning(versioning)
938
        except ClientError as err:
939
            raiseCLIError(err)
940

    
941
@command()
942
class store_group(_store_account_command):
943
    """Get user groups details for account"""
944

    
945
    def main(self):
946
        super(self.__class__, self).main()
947
        try:
948
            reply = self.client.get_account_group()
949
        except ClientError as err:
950
            raiseCLIError(err)
951
        print_dict(reply)
952

    
953
@command()
954
class store_setgroup(_store_account_command):
955
    """Create/update a new user group on account"""
956

    
957
    def main(self, groupname, *users):
958
        super(self.__class__, self).main()
959
        try:
960
            self.client.set_account_group(groupname, users)
961
        except ClientError as err:
962
            raiseCLIError(err)
963

    
964
@command()
965
class store_delgroup(_store_account_command):
966
    """Delete a user group on an account"""
967

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

    
975
@command()
976
class store_sharers(_store_account_command):
977
    """List the accounts that share objects with default account"""
978

    
979
    def __init__(self, arguments = {}):
980
        super(self.__class__, self).__init__(arguments)
981
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
982
        self.arguments['limit'] = IntArgument('show limited output', '--n', default=1000)
983
        self.arguments['marker'] = ValueArgument('show output greater then marker', '--marker')
984

    
985
    def main(self):
986
        super(self.__class__, self).main()
987
        try:
988
            accounts = self.client.get_sharing_accounts(marker=self.get_argument('marker'))
989
        except ClientError as err:
990
            raiseCLIError(err)
991

    
992
        for acc in accounts:
993
            stdout.write(bold(acc['name'])+' ')
994
            if self.get_argument('detail'):
995
                print_dict(acc, exclude='name', ident=18)
996
        if not self.get_argument('detail'):
997
            print
998

    
999
@command()
1000
class store_versions(_store_container_command):
1001
    """Get the version list of an object"""
1002

    
1003
    def main(self, container___path):
1004
        super(store_versions, self).main(container___path)
1005
        try:
1006
            versions = self.client.get_object_versionlist(self.path)
1007
        except ClientError as err:
1008
            raise CLIError(err)
1009

    
1010
        print('%s:%s versions'%(self.container,self.path))
1011
        for vitem in versions:
1012
            t = localtime(float(vitem[1]))
1013
            vid = bold(unicode(vitem[0]))
1014
            print('\t%s \t(%s)'%(vid, strftime('%d-%m-%Y %H:%M:%S', t)))