Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 43ee6ae1

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 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 as err:
101
                raiseCLIError(err, 'Error in --sharing',
102
                    details='Incorrect format',
103
                    importance=1)
104
            if key.lower() not in ('read', 'write'):
105
                raiseCLIError(err, 'Error in --sharing',
106
                    details='Invalid permission 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
        raiseCLIError(None,
171
            'Date Argument Error',
172
            details='%s not a valid date. correct formats:\n\t%s'\
173
            % (datestr, self.INPUT_FORMATS))
174

    
175

    
176
# Command specs
177

    
178

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

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

    
196

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

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

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

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

    
213

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

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

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

    
253
    def main(self, container_with_path=None, path_is_optional=True):
254
        super(_store_container_command, self).main()
255
        if container_with_path is not None:
256
            self.extract_container_and_path(container_with_path,
257
                path_is_optional)
258
            self.client.container = self.container
259
        elif self.get_argument('container') is not None:
260
            self.client.container = self.get_argument('container')
261
        self.container = self.client.container
262

    
263

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

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

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

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

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

    
390

    
391
@command(pithos_cmds)
392
class store_mkdir(_store_container_command):
393
    """Create a directory"""
394

    
395
    def main(self, container___directory):
396
        super(self.__class__,
397
            self).main(container___directory, path_is_optional=False)
398
        try:
399
            self.client.create_directory(self.path)
400
        except ClientError as err:
401
            raiseCLIError(err)
402

    
403

    
404
@command(pithos_cmds)
405
class store_create(_store_container_command):
406
    """Create a container or a directory object"""
407

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

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

    
432

    
433
@command(pithos_cmds)
434
class store_copy(_store_container_command):
435
    """Copy an object"""
436

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

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

    
469

    
470
@command(pithos_cmds)
471
class store_move(_store_container_command):
472
    """Copy an object"""
473

    
474
    def __init__(self, arguments={}):
475
        super(self.__class__, self).__init__(arguments)
476

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

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

    
507

    
508
@command(pithos_cmds)
509
class store_append(_store_container_command):
510
    """Append local file to (existing) remote object"""
511

    
512
    def __init__(self, arguments={}):
513
        super(self.__class__, self).__init__(arguments)
514
        self.arguments['progress_bar'] = ProgressBarArgument(\
515
            'do not show progress bar', '--no-progress-bar', False)
516

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

    
536

    
537
@command(pithos_cmds)
538
class store_truncate(_store_container_command):
539
    """Truncate remote file up to a size"""
540

    
541
    def main(self, container___path, size=0):
542
        super(self.__class__,
543
            self).main(container___path, path_is_optional=False)
544
        try:
545
            self.client.truncate_object(self.path, size)
546
        except ClientError as err:
547
            raiseCLIError(err)
548

    
549

    
550
@command(pithos_cmds)
551
class store_overwrite(_store_container_command):
552
    """Overwrite part (from start to end) of a remote file"""
553

    
554
    def __init__(self, arguments={}):
555
        super(self.__class__, self).__init__(arguments)
556
        self.arguments['progress_bar'] = ProgressBarArgument(\
557
            'do not show progress bar', '--no-progress-bar', False)
558

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

    
580

    
581
@command(pithos_cmds)
582
class store_manifest(_store_container_command):
583
    """Create a remote file with uploaded parts by manifestation"""
584

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

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

    
614

    
615
@command(pithos_cmds)
616
class store_upload(_store_container_command):
617
    """Upload a file"""
618

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

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

    
683

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

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

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

    
719

    
720
@command(pithos_cmds)
721
class store_download(_store_container_command):
722
    """Download a file"""
723

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

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

    
749
        # setup output stream
750
        if local_path is None:
751
            out = stdout
752
        else:
753
            try:
754
                if self.get_argument('resume'):
755
                    out = open(local_path, 'rwb+')
756
                else:
757
                    out = open(local_path, 'wb+')
758
            except IOError as err:
759
                raiseCLIError(err, 'Cannot write to file %s' % local_path, 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
            raiseCLIError(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
            raiseCLIError(None,
936
            '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 as err:
1049
            raiseCLIError(err, '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
            raiseCLIError(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)))