Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (41.9 kB)

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

    
34
from kamaki.cli import command#, set_api_description
35
from kamaki.clients.utils import filter_in
36
from kamaki.cli.errors import CLIError, raiseCLIError
37
from kamaki.cli.utils import format_size, print_dict, pretty_keys, print_list
38
from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
39
#set_api_description('store', 'Pithos+ storage commands')
40
API_DESCRIPTION = {'store':'Pithos+ storage commands'}
41
from kamaki.clients.pithos import PithosClient, ClientError
42
from colors import bold
43
from sys import stdout, exit
44
import signal
45
from time import localtime, strftime, strptime, mktime
46
from datetime import datetime as dtm
47

    
48
from progress.bar import IncrementalBar
49

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

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

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

    
75
class ProgressBarArgument(FlagArgument, IncrementalBar):
76

    
77
    def __init__(self, help='', parsed_name='', default=True):
78
        self.suffix = '%(percent)d%%'
79
        super(ProgressBarArgument, self).__init__(help, parsed_name, default)
80

    
81
    @property 
82
    def value(self):
83
        return getattr(self, '_value', self.default)
84
    @value.setter 
85
    def value(self, newvalue):
86
        """By default, it is on (True)"""
87
        self._value = not newvalue
88

    
89
    def get_generator(self, message):
90
        _message_len = 25
91
        def progress_gen(n):
92
            msg = message.ljust(_message_len)
93
            for i in self.iter(range(n)):
94
                yield
95
            yield
96
        return progress_gen
97

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

    
124
class RangeArgument(ValueArgument):
125
    @property 
126
    def value(self):
127
        return getattr(self, '_value', self.default)
128
    @value.setter
129
    def value(self, newvalue):
130
        if newvalue is None:
131
            self._value = self.default
132
            return
133
        (start, end) = newvalue.split('_')
134
        (start, end) = (int(start), int(end))
135
        self._value = '%s-%s'%(start, end)
136

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

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

    
144
    @property 
145
    def value(self):
146
        return getattr(self, '_value', self.default)
147
    @value.setter
148
    def value(self, newvalue):
149
        if newvalue is None:
150
            return
151
        self._value = self.format_date(newvalue)
152

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

    
164
class _pithos_init(object):
165
    def __init__(self, arguments={}):
166
        self.arguments = arguments
167
        try:
168
            self.config = self.get_argument('config')
169
        except KeyError:
170
            pass
171

    
172
    def get_argument(self, arg_name):
173
        return self.arguments[arg_name].value
174

    
175
    def main(self):
176
        self.token = self.config.get('store', 'token') or self.config.get('global', 'token')
177
        self.base_url = self.config.get('store', 'url') or self.config.get('global', 'url')
178
        self.account = self.config.get('store', 'account') or self.config.get('global', 'account')
179
        self.container = self.config.get('store', 'container') or self.config.get('global',
180
            'container')
181
        self.client = PithosClient(base_url=self.base_url, token=self.token, account=self.account,
182
            container=self.container)
183

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

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

    
191
    def generator(self, message):
192
       return None 
193

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

    
199
class _store_container_command(_store_account_command):
200
    """Base class for container level storage commands"""
201

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

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

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

    
241
"""
242
@command()
243
class store_test(_store_container_command):
244
    "Test stuff something""
245

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

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

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

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

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

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

    
362
@command()
363
class store_mkdir(_store_container_command):
364
    """Create a directory"""
365

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

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

    
377

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

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

    
397
@command()
398
class store_copy(_store_container_command):
399
    """Copy an object"""
400

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

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

    
426
@command()
427
class store_move(_store_container_command):
428
    """Copy an object"""
429

    
430
    def __init__(self, arguments={}):
431
        super(self.__class__, self).__init__(arguments)
432

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

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

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

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

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

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

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

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

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

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

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

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

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

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