Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 6069b53b

History | View | Annotate | Download (46.1 kB)

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

    
34
from kamaki.cli import command
35
from kamaki.cli.command_tree import CommandTree
36
from kamaki.cli.errors import CLIError, raiseCLIError
37
from kamaki.cli.utils import format_size, print_dict, pretty_keys
38
from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
39
from kamaki.cli.argument import KeyValueArgument
40
from kamaki.cli.argument import ProgressBarArgument
41
from kamaki.cli.commands import _command_init
42
from kamaki.clients.pithos import PithosClient, ClientError
43
from kamaki.cli.utils import bold
44
from sys import stdout
45
from time import localtime, strftime
46
from datetime import datetime as dtm
47

    
48

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

    
52

    
53
# Argument functionality
54

    
55

    
56
class DelimiterArgument(ValueArgument):
57
    """
58
    :value type: string
59
    :value returns: given string or /
60
    """
61

    
62
    def __init__(self, caller_obj, help='', parsed_name=None, default=None):
63
        super(DelimiterArgument, self).__init__(help, parsed_name, default)
64
        self.caller_obj = caller_obj
65

    
66
    @property
67
    def value(self):
68
        if self.caller_obj.get_argument('recursive'):
69
            return '/'
70
        return getattr(self, '_value', self.default)
71

    
72
    @value.setter
73
    def value(self, newvalue):
74
        self._value = newvalue
75

    
76

    
77
class SharingArgument(ValueArgument):
78
    """Set sharing (read and/or write) groups
79

80
    :value type: "read=term1,term2,... write=term1,term2,..."
81

82
    :value returns: {'read':['term1', 'term2', ...],
83
        'write':['term1', 'term2', ...]}
84
    """
85

    
86
    @property
87
    def value(self):
88
        return getattr(self, '_value', self.default)
89

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

    
116

    
117
class RangeArgument(ValueArgument):
118
    """
119
    :value type: string of the form <start>-<end>
120
        where <start> and <end> are integers
121

122
    :value returns: the input string, after type checking <start> and <end>
123
    """
124

    
125
    @property
126
    def value(self):
127
        return getattr(self, '_value', self.default)
128

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

    
138

    
139
class DateArgument(ValueArgument):
140
    """
141
    :value type: a string formated in an acceptable date format
142

143
    :value returns: same date in first of DATE_FORMATS
144
    """
145

    
146
    DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
147
        "%A, %d-%b-%y %H:%M:%S GMT",
148
        "%a, %d %b %Y %H:%M:%S GMT"]
149

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

    
152
    @property
153
    def value(self):
154
        return getattr(self, '_value', self.default)
155

    
156
    @value.setter
157
    def value(self, newvalue):
158
        if newvalue is None:
159
            return
160
        self._value = self.format_date(newvalue)
161

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

    
174

    
175
# Command specs
176

    
177

    
178
class _pithos_init(_command_init):
179
    """Initialize a pithos+ kamaki client"""
180

    
181
    def main(self):
182
        self.token = self.config.get('store', 'token')\
183
            or self.config.get('global', 'token')
184
        self.base_url = self.config.get('store', 'url')\
185
            or self.config.get('global', 'url')
186
        self.account = self.config.get('store', 'account')\
187
            or self.config.get('global', 'account')
188
        self.container = self.config.get('store', 'container')\
189
            or self.config.get('global', 'container')
190
        self.client = PithosClient(base_url=self.base_url,
191
            token=self.token,
192
            account=self.account,
193
            container=self.container)
194

    
195

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

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

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

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

    
212

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

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

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

    
249
    def main(self, container_with_path=None, path_is_optional=True):
250
        super(_store_container_command, self).main()
251
        if container_with_path is not None:
252
            self.extract_container_and_path(container_with_path,
253
                path_is_optional)
254
            self.client.container = self.container
255
        elif self.get_argument('container') is not None:
256
            self.client.container = self.get_argument('container')
257
        self.container = self.client.container
258

    
259

    
260
@command(pithos_cmds)
261
class store_list(_store_container_command):
262
    """List containers, object trees or objects in a directory
263
    """
264

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

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

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

    
358
    def main(self, container____path__=None):
359
        super(self.__class__, self).main(container____path__)
360
        try:
361
            if self.container is None:
362
                r = self.client.account_get(limit=self.get_argument('limit'),
363
                    marker=self.get_argument('marker'),
364
                    if_modified_since=self.get_argument('if_modified_since'),
365
                    if_unmodified_since=self.get_argument(\
366
                        'if_unmodified_since'),
367
                    until=self.get_argument('until'),
368
                    show_only_shared=self.get_argument('shared'))
369
                self.print_containers(r.json)
370
            else:
371
                r = self.client.container_get(limit=self.get_argument('limit'),
372
                    marker=self.get_argument('marker'),
373
                    prefix=self.get_argument('prefix'),
374
                    delimiter=self.get_argument('delimiter'),
375
                    path=self.get_argument('path'),
376
                    if_modified_since=self.get_argument('if_modified_since'),
377
                    if_unmodified_since=self.get_argument(\
378
                        'if_unmodified_since'),
379
                    until=self.get_argument('until'),
380
                    meta=self.get_argument('meta'),
381
                    show_only_shared=self.get_argument('shared'))
382
                self.print_objects(r.json)
383
        except ClientError as err:
384
            raiseCLIError(err)
385

    
386

    
387
@command(pithos_cmds)
388
class store_mkdir(_store_container_command):
389
    """Create a directory"""
390

    
391
    def main(self, container___directory):
392
        super(self.__class__,
393
            self).main(container___directory, path_is_optional=False)
394
        try:
395
            self.client.create_directory(self.path)
396
        except ClientError as err:
397
            raiseCLIError(err)
398

    
399

    
400
@command(pithos_cmds)
401
class store_create(_store_container_command):
402
    """Create a container or a directory object"""
403

    
404
    def __init__(self, arguments={}):
405
        super(self.__class__, self).__init__(arguments)
406
        self.arguments['versioning'] = \
407
            ValueArgument('set container versioning (auto/none)',
408
            '--versioning')
409
        self.arguments['quota'] =\
410
            IntArgument('set default container quota', '--quota')
411
        self.arguments['meta'] =\
412
            KeyValueArgument(
413
                'set container metadata (can be repeated)', '--meta')
414
            #  MetaArgument('set container metadata', '--meta')
415

    
416
    def main(self, container____directory__):
417
        super(self.__class__, self).main(container____directory__)
418
        try:
419
            if self.path is None:
420
                self.client.container_put(quota=self.get_argument('quota'),
421
                    versioning=self.get_argument('versioning'),
422
                    metadata=self.get_argument('meta'))
423
            else:
424
                self.client.create_directory(self.path)
425
        except ClientError as err:
426
            raiseCLIError(err)
427

    
428

    
429
@command(pithos_cmds)
430
class store_copy(_store_container_command):
431
    """Copy an object"""
432

    
433
    def __init__(self, arguments={}):
434
        super(self.__class__, self).__init__(arguments)
435
        self.arguments['source_version'] = ValueArgument(\
436
            'copy specific version', '--source-version')
437
        self.arguments['public'] = ValueArgument(\
438
            'make object publicly accessible', '--public')
439
        self.arguments['content_type'] = ValueArgument(\
440
            'change object\'s content type', '--content-type')
441
        self.arguments['delimiter'] = DelimiterArgument(self,
442
            parsed_name='--delimiter',
443
            help=u'copy objects prefixed as src_object + delimiter')
444
        self.arguments['recursive'] = FlagArgument(
445
            'mass copy with delimiter /', ('-r', '--recursive'))
446

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

    
465

    
466
@command(pithos_cmds)
467
class store_move(_store_container_command):
468
    """Copy an object"""
469

    
470
    def __init__(self, arguments={}):
471
        super(self.__class__, self).__init__(arguments)
472

    
473
        self.arguments['source_version'] = ValueArgument(\
474
            'copy specific version', '--source-version')
475
        self.arguments['public'] = FlagArgument(\
476
            'make object publicly accessible', '--public')
477
        self.arguments['content_type'] = ValueArgument(\
478
            'change object\'s content type', '--content-type')
479
        self.arguments['delimiter'] = DelimiterArgument(self,
480
            parsed_name='--delimiter',
481
            help='move objects prefixed as src_object + delimiter')
482
        self.arguments['recursive'] = FlagArgument(\
483
            'copy with delimiter /', ('-r', '--recursive'))
484

    
485
    def main(self, source_container___path, destination_container____path__):
486
        super(self.__class__,
487
            self).main(source_container___path, path_is_optional=False)
488
        try:
489
            dst = destination_container____path__.split(':')
490
            dst_cont = dst[0]
491
            dst_path = dst[1] if len(dst) > 1 else False
492
            self.client.move_object(src_container=self.container,
493
                src_object=self.path,
494
                dst_container=dst_cont,
495
                dst_object=dst_path,
496
                source_version=self.get_argument('source_version'),
497
                public=self.get_argument('public'),
498
                content_type=self.get_argument('content_type'),
499
                delimiter=self.get_argument('delimiter'))
500
        except ClientError as err:
501
            raiseCLIError(err)
502

    
503

    
504
@command(pithos_cmds)
505
class store_append(_store_container_command):
506
    """Append local file to (existing) remote object"""
507

    
508
    def __init__(self, arguments={}):
509
        super(self.__class__, self).__init__(arguments)
510
        self.arguments['progress_bar'] = ProgressBarArgument(\
511
            'do not show progress bar', '--no-progress-bar', False)
512

    
513
    def main(self, local_path, container___path):
514
        super(self.__class__,
515
            self).main(container___path, path_is_optional=False)
516
        try:
517
            f = open(local_path, 'r')
518
            progress_bar = self.arguments['progress_bar']
519
            try:
520
                upload_cb = progress_bar.get_generator('Appending blocks')
521
            except Exception:
522
                upload_cb = None
523
            self.client.append_object(object=self.path,
524
                source_file=f,
525
                upload_cb=upload_cb)
526
        except ClientError as err:
527
            progress_bar.finish()
528
            raiseCLIError(err)
529
        finally:
530
            progress_bar.finish()
531

    
532

    
533
@command(pithos_cmds)
534
class store_truncate(_store_container_command):
535
    """Truncate remote file up to a size"""
536

    
537
    def main(self, container___path, size=0):
538
        super(self.__class__,
539
            self).main(container___path, path_is_optional=False)
540
        try:
541
            self.client.truncate_object(self.path, size)
542
        except ClientError as err:
543
            raiseCLIError(err)
544

    
545

    
546
@command(pithos_cmds)
547
class store_overwrite(_store_container_command):
548
    """Overwrite part (from start to end) of a remote file"""
549

    
550
    def __init__(self, arguments={}):
551
        super(self.__class__, self).__init__(arguments)
552
        self.arguments['progress_bar'] = ProgressBarArgument(\
553
            'do not show progress bar', '--no-progress-bar', False)
554

    
555
    def main(self, local_path, container___path, start, end):
556
        super(self.__class__,
557
            self).main(container___path, path_is_optional=False)
558
        try:
559
            f = open(local_path, 'r')
560
            progress_bar = self.arguments['progress_bar']
561
            try:
562
                upload_cb = progress_bar.get_generator('Overwritting blocks')
563
            except Exception:
564
                upload_cb = None
565
            self.client.overwrite_object(object=self.path,
566
                start=start,
567
                end=end,
568
                source_file=f,
569
                upload_cb=upload_cb)
570
        except ClientError as err:
571
            progress_bar.finish()
572
            raiseCLIError(err)
573
        finally:
574
            progress_bar.finish()
575

    
576

    
577
@command(pithos_cmds)
578
class store_manifest(_store_container_command):
579
    """Create a remote file with uploaded parts by manifestation"""
580

    
581
    def __init__(self, arguments={}):
582
        super(self.__class__, self).__init__(arguments)
583
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
584
        self.arguments['content_encoding'] = ValueArgument(\
585
            'provide the object MIME content type', '--content-encoding')
586
        self.arguments['content_disposition'] = ValueArgument(\
587
            'provide the presentation style of the object',
588
            '--content-disposition')
589
        self.arguments['content_type'] = ValueArgument(\
590
            'create object with specific content type', '--content-type')
591
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
592
            help='define object sharing policy ' +\
593
            '( "read=user1,grp1,user2,... write=user1,grp2,..." )')
594
        self.arguments['public'] = FlagArgument(\
595
            'make object publicly accessible', '--public')
596

    
597
    def main(self, container___path):
598
        super(self.__class__,
599
            self).main(container___path, path_is_optional=False)
600
        try:
601
            self.client.create_object_by_manifestation(self.path,
602
                content_encoding=self.get_argument('content_encoding'),
603
                content_disposition=self.get_argument('content_disposition'),
604
                content_type=self.get_argument('content_type'),
605
                sharing=self.get_argument('sharing'),
606
                public=self.get_argument('public'))
607
        except ClientError as err:
608
            raiseCLIError(err)
609

    
610

    
611
@command(pithos_cmds)
612
class store_upload(_store_container_command):
613
    """Upload a file"""
614

    
615
    def __init__(self, arguments={}):
616
        super(self.__class__, self).__init__(arguments)
617
        self.arguments['use_hashes'] = FlagArgument(\
618
            'provide hashmap file instead of data', '--use-hashes')
619
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
620
        self.arguments['unchunked'] = FlagArgument(\
621
            'avoid chunked transfer mode', '--unchunked')
622
        self.arguments['content_encoding'] = ValueArgument(\
623
            'provide the object MIME content type', '--content-encoding')
624
        self.arguments['content_disposition'] = ValueArgument(\
625
            'provide the presentation style of the object',
626
            '--content-disposition')
627
        self.arguments['content_type'] = ValueArgument(\
628
            'create object with specific content type', '--content-type')
629
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
630
            help='define sharing object policy ' +\
631
            '( "read=user1,grp1,user2,... write=user1,grp2,...')
632
        self.arguments['public'] = FlagArgument(\
633
            'make object publicly accessible', '--public')
634
        self.arguments['poolsize'] = IntArgument(\
635
            'set pool size', '--with-pool-size')
636
        self.arguments['progress_bar'] = ProgressBarArgument(\
637
            'do not show progress bar', '--no-progress-bar', False)
638

    
639
    def main(self, local_path, container____path__):
640
        super(self.__class__, self).main(container____path__)
641
        remote_path = local_path if self.path is None else self.path
642
        poolsize = self.get_argument('poolsize')
643
        if poolsize is not None:
644
            self.client.POOL_SIZE = int(poolsize)
645
        params = dict(content_encoding=self.get_argument('content_encoding'),
646
            content_type=self.get_argument('content_type'),
647
            content_disposition=self.get_argument('content_disposition'),
648
            sharing=self.get_argument('sharing'),
649
            public=self.get_argument('public'))
650
        try:
651
            progress_bar = self.arguments['progress_bar']
652
            hash_bar = progress_bar.clone()
653
            with open(local_path) as f:
654
                if self.get_argument('unchunked'):
655
                    self.client.upload_object_unchunked(remote_path, f,
656
                    etag=self.get_argument('etag'),
657
                    withHashFile=self.get_argument('use_hashes'),
658
                    **params)
659
                else:
660
                    hash_cb = hash_bar.get_generator(\
661
                        'Calculating block hashes')
662
                    upload_cb = progress_bar.get_generator('Uploading')
663
                    self.client.upload_object(remote_path, f,
664
                        hash_cb=hash_cb,
665
                        upload_cb=upload_cb,
666
                        **params)
667
                    progress_bar.finish()
668
                    hash_bar.finish()
669
        except ClientError as err:
670
            progress_bar.finish()
671
            hash_bar.finish()
672
            raiseCLIError(err)
673
        except IOError as err:
674
            progress_bar.finish()
675
            hash_bar.finish()
676
            raiseCLIError(err,
677
                message='Failed to read form file %s' % local_path,
678
                importance=2)
679
        print 'Upload completed'
680

    
681

    
682
@command(pithos_cmds)
683
class store_cat(_store_container_command):
684
    """Print a file to console"""
685

    
686
    def __init__(self, arguments={}):
687
        super(self.__class__, self).__init__(arguments)
688
        self.arguments['range'] =\
689
            RangeArgument('show range of data', '--range')
690
        self.arguments['if_match'] =\
691
            ValueArgument('show output if ETags match', '--if-match')
692
        self.arguments['if_none_match'] =\
693
            ValueArgument('show output if ETags match', '--if-none-match')
694
        self.arguments['if_modified_since'] =\
695
            DateArgument('show output modified since then',
696
            '--if-modified-since')
697
        self.arguments['if_unmodified_since'] =\
698
            DateArgument('show output unmodified since then',
699
            '--if-unmodified-since')
700
        self.arguments['object_version'] =\
701
            ValueArgument('get the specific version', '--object-version')
702

    
703
    def main(self, container___path):
704
        super(self.__class__,
705
            self).main(container___path, path_is_optional=False)
706
        try:
707
            self.client.download_object(self.path, stdout,
708
            range=self.get_argument('range'),
709
            version=self.get_argument('object_version'),
710
            if_match=self.get_argument('if_match'),
711
            if_none_match=self.get_argument('if_none_match'),
712
            if_modified_since=self.get_argument('if_modified_since'),
713
            if_unmodified_since=self.get_argument('if_unmodified_since'))
714
        except ClientError as err:
715
            raiseCLIError(err)
716

    
717

    
718
@command(pithos_cmds)
719
class store_download(_store_container_command):
720
    """Download a file"""
721

    
722
    def __init__(self, arguments={}):
723
        super(self.__class__, self).__init__(arguments)
724
        self.arguments['resume'] = FlagArgument(parsed_name='--resume',
725
            help='Resume a previous download instead of overwritting it')
726
        self.arguments['range'] = RangeArgument(\
727
            'show range of data', '--range')
728
        self.arguments['if_match'] = ValueArgument(\
729
            'show output if ETags match', '--if-match')
730
        self.arguments['if_none_match'] = ValueArgument(\
731
            'show output if ETags match', '--if-none-match')
732
        self.arguments['if_modified_since'] = DateArgument(\
733
            'show output modified since then', '--if-modified-since')
734
        self.arguments['if_unmodified_since'] = DateArgument(\
735
            'show output unmodified since then', '--if-unmodified-since')
736
        self.arguments['object_version'] = ValueArgument(\
737
            'get the specific version', '--object-version')
738
        self.arguments['poolsize'] = IntArgument(\
739
            'set pool size', '--with-pool-size')
740
        self.arguments['progress_bar'] = ProgressBarArgument(\
741
            'do not show progress bar', '--no-progress-bar', False)
742

    
743
    def main(self, container___path, local_path):
744
        super(self.__class__,
745
            self).main(container___path, path_is_optional=False)
746

    
747
        # setup output stream
748
        if local_path is None:
749
            out = stdout
750
        else:
751
            try:
752
                if self.get_argument('resume'):
753
                    out = open(local_path, 'rwb+')
754
                else:
755
                    out = open(local_path, 'wb+')
756
            except IOError as err:
757
                raise CLIError(message='Cannot write to file %s - %s'\
758
                    % (local_path, unicode(err)),
759
                    importance=1)
760
        poolsize = self.get_argument('poolsize')
761
        if poolsize is not None:
762
            self.client.POOL_SIZE = int(poolsize)
763

    
764
        try:
765
            progress_bar = self.arguments['progress_bar']
766
            download_cb = progress_bar.get_generator('Downloading')
767
            self.client.download_object(self.path, out,
768
                download_cb=download_cb,
769
                range=self.get_argument('range'),
770
                version=self.get_argument('object_version'),
771
                if_match=self.get_argument('if_match'),
772
                resume=self.get_argument('resume'),
773
                if_none_match=self.get_argument('if_none_match'),
774
                if_modified_since=self.get_argument('if_modified_since'),
775
                if_unmodified_since=self.get_argument('if_unmodified_since'))
776
            progress_bar.finish()
777
        except ClientError as err:
778
            progress_bar.finish()
779
            raiseCLIError(err)
780
        except KeyboardInterrupt:
781
            from threading import enumerate as activethreads
782
            stdout.write('\nFinishing active threads ')
783
            for thread in activethreads():
784
                stdout.flush()
785
                try:
786
                    thread.join()
787
                    stdout.write('.')
788
                except RuntimeError:
789
                    continue
790
            progress_bar.finish()
791
            print('\ndownload canceled by user')
792
            if local_path is not None:
793
                print('to resume, re-run with --resume')
794
        except Exception as e:
795
            progress_bar.finish()
796
            raise e
797
        print
798

    
799

    
800
@command(pithos_cmds)
801
class store_hashmap(_store_container_command):
802
    """Get the hashmap of an object"""
803

    
804
    def __init__(self, arguments={}):
805
        super(self.__class__, self).__init__(arguments)
806
        self.arguments['if_match'] =\
807
            ValueArgument('show output if ETags match', '--if-match')
808
        self.arguments['if_none_match'] =\
809
            ValueArgument('show output if ETags match', '--if-none-match')
810
        self.arguments['if_modified_since'] =\
811
            DateArgument('show output modified since then',
812
            '--if-modified-since')
813
        self.arguments['if_unmodified_since'] =\
814
            DateArgument('show output unmodified since then',
815
            '--if-unmodified-since')
816
        self.arguments['object_version'] =\
817
            ValueArgument('get the specific version', '--object-version')
818

    
819
    def main(self, container___path):
820
        super(self.__class__,
821
            self).main(container___path, path_is_optional=False)
822
        try:
823
            data = self.client.get_object_hashmap(self.path,
824
                version=self.arguments('object_version'),
825
                if_match=self.arguments('if_match'),
826
                if_none_match=self.arguments('if_none_match'),
827
                if_modified_since=self.arguments('if_modified_since'),
828
                if_unmodified_since=self.arguments('if_unmodified_since'))
829
        except ClientError as err:
830
            raiseCLIError(err)
831
        print_dict(data)
832

    
833

    
834
@command(pithos_cmds)
835
class store_delete(_store_container_command):
836
    """Delete a container [or an object]"""
837

    
838
    def __init__(self, arguments={}):
839
        super(self.__class__, self).__init__(arguments)
840
        self.arguments['until'] = DateArgument(\
841
            'remove history until that date', '--until')
842
        self.arguments['recursive'] = FlagArgument(\
843
            'empty dir or container and delete (if dir)',
844
            ('-r', '--recursive'))
845
        self.arguments['delimiter'] = DelimiterArgument(self,
846
            parsed_name='--delimiter',
847
            help='delete objects prefixed with <object><delimiter>')
848

    
849
    def main(self, container____path__):
850
        super(self.__class__, self).main(container____path__)
851
        try:
852
            if self.path is None:
853
                self.client.del_container(until=self.get_argument('until'),
854
                    delimiter=self.get_argument('delimiter'))
855
            else:
856
                # self.client.delete_object(self.path)
857
                self.client.del_object(self.path,
858
                    until=self.get_argument('until'),
859
                    delimiter=self.get_argument('delimiter'))
860
        except ClientError as err:
861
            raiseCLIError(err)
862

    
863

    
864
@command(pithos_cmds)
865
class store_purge(_store_container_command):
866
    """Purge a container"""
867

    
868
    def main(self, container):
869
        super(self.__class__, self).main(container)
870
        try:
871
            self.client.purge_container()
872
        except ClientError as err:
873
            raiseCLIError(err)
874

    
875

    
876
@command(pithos_cmds)
877
class store_publish(_store_container_command):
878
    """Publish an object"""
879

    
880
    def main(self, container___path):
881
        super(self.__class__,
882
            self).main(container___path, path_is_optional=False)
883
        try:
884
            self.client.publish_object(self.path)
885
        except ClientError as err:
886
            raiseCLIError(err)
887

    
888

    
889
@command(pithos_cmds)
890
class store_unpublish(_store_container_command):
891
    """Unpublish an object"""
892

    
893
    def main(self, container___path):
894
        super(self.__class__,
895
            self).main(container___path, path_is_optional=False)
896
        try:
897
            self.client.unpublish_object(self.path)
898
        except ClientError as err:
899
            raiseCLIError(err)
900

    
901

    
902
@command(pithos_cmds)
903
class store_permissions(_store_container_command):
904
    """Get object read / write permissions """
905

    
906
    def main(self, container___path):
907
        super(self.__class__,
908
            self).main(container___path, path_is_optional=False)
909
        try:
910
            reply = self.client.get_object_sharing(self.path)
911
            print_dict(reply)
912
        except ClientError as err:
913
            raiseCLIError(err)
914

    
915

    
916
@command(pithos_cmds)
917
class store_setpermissions(_store_container_command):
918
    """Set sharing permissions """
919

    
920
    def format_permition_dict(self, permissions):
921
        read = False
922
        write = False
923
        for perms in permissions:
924
            splstr = perms.split('=')
925
            if 'read' == splstr[0]:
926
                read = [user_or_group.strip() \
927
                for user_or_group in splstr[1].split(',')]
928
            elif 'write' == splstr[0]:
929
                write = [user_or_group.strip() \
930
                for user_or_group in splstr[1].split(',')]
931
            else:
932
                read = False
933
                write = False
934
        if not read and not write:
935
            raise CLIError(importance=0,
936
                message='Usage:\tread=<groups,users> write=<groups,users>')
937
        return (read, write)
938

    
939
    def main(self, container___path, *permissions):
940
        super(self.__class__,
941
            self).main(container___path, path_is_optional=False)
942
        (read, write) = self.format_permition_dict(permissions)
943
        try:
944
            self.client.set_object_sharing(self.path,
945
                read_permition=read, write_permition=write)
946
        except ClientError as err:
947
            raiseCLIError(err)
948

    
949

    
950
@command(pithos_cmds)
951
class store_delpermissions(_store_container_command):
952
    """Delete all sharing permissions"""
953

    
954
    def main(self, container___path):
955
        super(self.__class__,
956
            self).main(container___path, path_is_optional=False)
957
        try:
958
            self.client.del_object_sharing(self.path)
959
        except ClientError as err:
960
            raiseCLIError(err)
961

    
962

    
963
@command(pithos_cmds)
964
class store_info(_store_container_command):
965
    """Get information for account [, container [or object]]"""
966

    
967
    def __init__(self, arguments={}):
968
        super(self.__class__, self).__init__(arguments)
969
        self.arguments['object_version'] =\
970
            ValueArgument(parsed_name='--object-version',
971
            help='show specific version \ (applies only for objects)')
972

    
973
    def main(self, container____path__=None):
974
        super(self.__class__, self).main(container____path__)
975
        try:
976
            if self.container is None:
977
                reply = self.client.get_account_info()
978
            elif self.path is None:
979
                reply = self.client.get_container_info(self.container)
980
            else:
981
                reply = self.client.get_object_info(self.path,
982
                    version=self.get_argument('object_version'))
983
        except ClientError as err:
984
            raiseCLIError(err)
985
        print_dict(reply)
986

    
987

    
988
@command(pithos_cmds)
989
class store_meta(_store_container_command):
990
    """Get custom meta-content for account [, container [or object]]"""
991

    
992
    def __init__(self, arguments={}):
993
        super(self.__class__, self).__init__(arguments)
994
        self.arguments['detail'] =\
995
            FlagArgument('show detailed output', '-l')
996
        self.arguments['until'] =\
997
            DateArgument('show metadata until then', '--until')
998
        self.arguments['object_version'] =\
999
            ValueArgument(parsed_name='--object-version',
1000
            help='show specific version \ (applies only for objects)')
1001

    
1002
    def main(self, container____path__=None):
1003
        super(self.__class__, self).main(container____path__)
1004

    
1005
        detail = self.get_argument('detail')
1006
        try:
1007
            until = self.get_argument('until')
1008
            if self.container is None:
1009
                print(bold(self.client.account))
1010
                if detail:
1011
                    reply = self.client.get_account_info(until=until)
1012
                else:
1013
                    reply = self.client.get_account_meta(until=until)
1014
                    reply = pretty_keys(reply, '-')
1015
            elif self.path is None:
1016
                print(bold('%s: %s' % (self.client.account, self.container)))
1017
                if detail:
1018
                    reply = self.client.get_container_info(until=until)
1019
                else:
1020
                    cmeta = self.client.get_container_meta(until=until)
1021
                    ometa = self.client.get_container_object_meta(until=until)
1022
                    reply = {'container-meta': pretty_keys(cmeta, '-'),
1023
                        'object-meta': pretty_keys(ometa, '-')}
1024
            else:
1025
                print(bold('%s: %s:%s'\
1026
                    % (self.client.account, self.container, self.path)))
1027
                version = self.get_argument('object_version')
1028
                if detail:
1029
                    reply = self.client.get_object_info(self.path,
1030
                        version=version)
1031
                else:
1032
                    reply = self.client.get_object_meta(self.path,
1033
                        version=version)
1034
                    reply = pretty_keys(pretty_keys(reply, '-'))
1035
        except ClientError as err:
1036
            raiseCLIError(err)
1037
        print_dict(reply)
1038

    
1039

    
1040
@command(pithos_cmds)
1041
class store_setmeta(_store_container_command):
1042
    """Set a new metadatum for account [, container [or object]]"""
1043

    
1044
    def main(self, metakey___metaval, container____path__=None):
1045
        super(self.__class__, self).main(container____path__)
1046
        try:
1047
            metakey, metavalue = metakey___metaval.split(':')
1048
        except ValueError:
1049
            raise CLIError(message='Usage:  metakey:metavalue', importance=1)
1050
        try:
1051
            if self.container is None:
1052
                self.client.set_account_meta({metakey: metavalue})
1053
            elif self.path is None:
1054
                self.client.set_container_meta({metakey: metavalue})
1055
            else:
1056
                self.client.set_object_meta(self.path, {metakey: metavalue})
1057
        except ClientError as err:
1058
            raiseCLIError(err)
1059

    
1060

    
1061
@command(pithos_cmds)
1062
class store_delmeta(_store_container_command):
1063
    """Delete an existing metadatum of account [, container [or object]]"""
1064

    
1065
    def main(self, metakey, container____path__=None):
1066
        super(self.__class__, self).main(container____path__)
1067
        try:
1068
            if self.container is None:
1069
                self.client.del_account_meta(metakey)
1070
            elif self.path is None:
1071
                self.client.del_container_meta(metakey)
1072
            else:
1073
                self.client.del_object_meta(self.path, metakey)
1074
        except ClientError as err:
1075
            raiseCLIError(err)
1076

    
1077

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

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

    
1093

    
1094
@command(pithos_cmds)
1095
class store_setquota(_store_account_command):
1096
    """Set new quota (in KB) for account [or container]"""
1097

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

    
1109

    
1110
@command(pithos_cmds)
1111
class store_versioning(_store_account_command):
1112
    """Get  versioning for account [or container ]"""
1113

    
1114
    def main(self, container=None):
1115
        super(self.__class__, self).main()
1116
        try:
1117
            if container is None:
1118
                reply = self.client.get_account_versioning()
1119
            else:
1120
                reply = self.client.get_container_versioning(container)
1121
        except ClientError as err:
1122
            raiseCLIError(err)
1123
        print_dict(reply)
1124

    
1125

    
1126
@command(pithos_cmds)
1127
class store_setversioning(_store_account_command):
1128
    """Set new versioning (auto, none) for account [or container]"""
1129

    
1130
    def main(self, versioning, container=None):
1131
        super(self.__class__, self).main()
1132
        try:
1133
            if container is None:
1134
                self.client.set_account_versioning(versioning)
1135
            else:
1136
                self.client.container = container
1137
                self.client.set_container_versioning(versioning)
1138
        except ClientError as err:
1139
            raiseCLIError(err)
1140

    
1141

    
1142
@command(pithos_cmds)
1143
class store_group(_store_account_command):
1144
    """Get user groups details for account"""
1145

    
1146
    def main(self):
1147
        super(self.__class__, self).main()
1148
        try:
1149
            reply = self.client.get_account_group()
1150
        except ClientError as err:
1151
            raiseCLIError(err)
1152
        print_dict(reply)
1153

    
1154

    
1155
@command(pithos_cmds)
1156
class store_setgroup(_store_account_command):
1157
    """Create/update a new user group on account"""
1158

    
1159
    def main(self, groupname, *users):
1160
        super(self.__class__, self).main()
1161
        try:
1162
            self.client.set_account_group(groupname, users)
1163
        except ClientError as err:
1164
            raiseCLIError(err)
1165

    
1166

    
1167
@command(pithos_cmds)
1168
class store_delgroup(_store_account_command):
1169
    """Delete a user group on an account"""
1170

    
1171
    def main(self, groupname):
1172
        super(self.__class__, self).main()
1173
        try:
1174
            self.client.del_account_group(groupname)
1175
        except ClientError as err:
1176
            raiseCLIError(err)
1177

    
1178

    
1179
@command(pithos_cmds)
1180
class store_sharers(_store_account_command):
1181
    """List the accounts that share objects with default account"""
1182

    
1183
    def __init__(self, arguments={}):
1184
        super(self.__class__, self).__init__(arguments)
1185
        self.arguments['detail'] =\
1186
            FlagArgument('show detailed output', '-l')
1187
        self.arguments['limit'] =\
1188
            IntArgument('show limited output', '--n', default=1000)
1189
        self.arguments['marker'] =\
1190
            ValueArgument('show output greater then marker', '--marker')
1191

    
1192
    def main(self):
1193
        super(self.__class__, self).main()
1194
        try:
1195
            marker = self.get_argument('marker')
1196
            accounts = self.client.get_sharing_accounts(marker=marker)
1197
        except ClientError as err:
1198
            raiseCLIError(err)
1199

    
1200
        for acc in accounts:
1201
            stdout.write(bold(acc['name']) + ' ')
1202
            if self.get_argument('detail'):
1203
                print_dict(acc, exclude='name', ident=4)
1204
        if not self.get_argument('detail'):
1205
            print
1206

    
1207

    
1208
@command(pithos_cmds)
1209
class store_versions(_store_container_command):
1210
    """Get the version list of an object"""
1211

    
1212
    def main(self, container___path):
1213
        super(store_versions, self).main(container___path)
1214
        try:
1215
            versions = self.client.get_object_versionlist(self.path)
1216
        except ClientError as err:
1217
            raise CLIError(err)
1218

    
1219
        print('%s:%s versions' % (self.container, self.path))
1220
        for vitem in versions:
1221
            t = localtime(float(vitem[1]))
1222
            vid = bold(unicode(vitem[0]))
1223
            print('\t%s \t(%s)' % (vid, strftime('%d-%m-%Y %H:%M:%S', t)))