Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 67cea04c

History | View | Annotate | Download (42.2 kB)

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

    
34
from kamaki.cli import command#, 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 kamaki.cli.commands 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 kamaki.cli.utils 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(message.ljust(message_len))
98
        return bar.get_generator()
99

    
100
class ProgressBar(IncrementalBar):
101
    suffix = '%(percent)d%% - %(eta)ds'
102
    def get_generator(self):
103
        def progress_gen(n):
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
        except IOError as err:
581
            raise CLIError(message='Failed to read form file %s'%local_path, importance=2, details=unicode(err))
582
        print 'Upload completed'
583

    
584
@command()
585
class store_download(_store_container_command):
586
    """Download a file"""
587

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

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

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

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

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

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

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

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

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

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

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

    
707
@command()
708
class store_publish(_store_container_command):
709
    """Publish an object"""
710

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

    
718
@command()
719
class store_unpublish(_store_container_command):
720
    """Unpublish an object"""
721

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

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

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

    
741
@command()
742
class store_setpermitions(_store_container_command):
743
    """Set sharing permitions"""
744

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

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

    
773
@command()
774
class store_delpermitions(_store_container_command):
775
    """Delete all sharing permitions"""
776

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

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

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

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

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

    
813
    def main(self, container____path__ = None):
814
        super(self.__class__, self).main(container____path__)
815

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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