Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos_cli.py @ 852a22e7

History | View | Annotate | Download (45.8 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 ProgressBarArgument
40
from kamaki.cli.commands import _command_init
41
from kamaki.clients.pithos import PithosClient, ClientError
42
from kamaki.cli.utils import bold
43
from sys import stdout
44
from time import localtime, strftime
45
from datetime import datetime as dtm
46

    
47

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

    
51

    
52
# Argument functionality
53

    
54

    
55
class DelimiterArgument(ValueArgument):
56
    def __init__(self, caller_obj, help='', parsed_name=None, default=None):
57
        super(DelimiterArgument, self).__init__(help, parsed_name, default)
58
        self.caller_obj = caller_obj
59

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

    
66
    @value.setter
67
    def value(self, newvalue):
68
        self._value = newvalue
69

    
70

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

    
82
    @value.setter
83
    def value(self, newvalue):
84
        if newvalue is None:
85
            self._value = self.default
86
        self._value = newvalue
87

    
88

    
89
class SharingArgument(ValueArgument):
90
    @property
91
    def value(self):
92
        return getattr(self, '_value', self.default)
93

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

    
120

    
121
class RangeArgument(ValueArgument):
122
    @property
123
    def value(self):
124
        return getattr(self, '_value', self.default)
125

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

    
135

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

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

    
143
    @property
144
    def value(self):
145
        return getattr(self, '_value', self.default)
146

    
147
    @value.setter
148
    def value(self, newvalue):
149
        if newvalue is None:
150
            return
151
        self._value = self.format_date(newvalue)
152

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

    
165

    
166
# Command specs
167

    
168

    
169
class _pithos_init(_command_init):
170
    def main(self):
171
        self.token = self.config.get('store', 'token')\
172
            or self.config.get('global', 'token')
173
        self.base_url = self.config.get('store', 'url')\
174
            or self.config.get('global', 'url')
175
        self.account = self.config.get('store', 'account')\
176
            or self.config.get('global', 'account')
177
        self.container = self.config.get('store', 'container')\
178
            or self.config.get('global', 'container')
179
        self.client = PithosClient(base_url=self.base_url,
180
            token=self.token,
181
            account=self.account,
182
            container=self.container)
183

    
184

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

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

    
193
    def generator(self, message):
194
        return None
195

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

    
201

    
202
class _store_container_command(_store_account_command):
203
    """Base class for container level storage commands"""
204

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

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

    
238
    def main(self, container_with_path=None, path_is_optional=True):
239
        super(_store_container_command, self).main()
240
        if container_with_path is not None:
241
            self.extract_container_and_path(container_with_path,
242
                path_is_optional)
243
            self.client.container = self.container
244
        elif self.get_argument('container') is not None:
245
            self.client.container = self.get_argument('container')
246
        self.container = self.client.container
247

    
248

    
249
@command(pithos_cmds)
250
class store_list(_store_container_command):
251
    """List containers, object trees or objects in a directory
252
    """
253

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

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

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

    
347
    def main(self, container____path__=None):
348
        super(self.__class__, self).main(container____path__)
349
        try:
350
            if self.container is None:
351
                r = self.client.account_get(limit=self.get_argument('limit'),
352
                    marker=self.get_argument('marker'),
353
                    if_modified_since=self.get_argument('if_modified_since'),
354
                    if_unmodified_since=self.get_argument(\
355
                        'if_unmodified_since'),
356
                    until=self.get_argument('until'),
357
                    show_only_shared=self.get_argument('shared'))
358
                self.print_containers(r.json)
359
            else:
360
                r = self.client.container_get(limit=self.get_argument('limit'),
361
                    marker=self.get_argument('marker'),
362
                    prefix=self.get_argument('prefix'),
363
                    delimiter=self.get_argument('delimiter'),
364
                    path=self.get_argument('path'),
365
                    if_modified_since=self.get_argument('if_modified_since'),
366
                    if_unmodified_since=self.get_argument(\
367
                        'if_unmodified_since'),
368
                    until=self.get_argument('until'),
369
                    meta=self.get_argument('meta'),
370
                    show_only_shared=self.get_argument('shared'))
371
                self.print_objects(r.json)
372
        except ClientError as err:
373
            raiseCLIError(err)
374

    
375

    
376
@command(pithos_cmds)
377
class store_mkdir(_store_container_command):
378
    """Create a directory"""
379

    
380
    def main(self, container___directory):
381
        super(self.__class__,
382
            self).main(container___directory, path_is_optional=False)
383
        try:
384
            self.client.create_directory(self.path)
385
        except ClientError as err:
386
            raiseCLIError(err)
387

    
388

    
389
@command(pithos_cmds)
390
class store_create(_store_container_command):
391
    """Create a container or a directory object"""
392

    
393
    def __init__(self, arguments={}):
394
        super(self.__class__, self).__init__(arguments)
395
        self.arguments['versioning'] = \
396
            ValueArgument('set container versioning (auto/none)',
397
            '--versioning')
398
        self.arguments['quota'] =\
399
            IntArgument('set default container quota', '--quota')
400
        self.arguments['meta'] =\
401
            MetaArgument('set container metadata', '--meta')
402

    
403
    def main(self, container____directory__):
404
        super(self.__class__, self).main(container____directory__)
405
        try:
406
            if self.path is None:
407
                self.client.container_put(quota=self.get_argument('quota'),
408
                    versioning=self.get_argument('versioning'),
409
                    metadata=self.get_argument('meta'))
410
            else:
411
                self.client.create_directory(self.path)
412
        except ClientError as err:
413
            raiseCLIError(err)
414

    
415

    
416
@command(pithos_cmds)
417
class store_copy(_store_container_command):
418
    """Copy an object"""
419

    
420
    def __init__(self, arguments={}):
421
        super(self.__class__, self).__init__(arguments)
422
        self.arguments['source_version'] = ValueArgument(\
423
            'copy specific version', '--source-version')
424
        self.arguments['public'] = ValueArgument(\
425
            'make object publicly accessible', '--public')
426
        self.arguments['content_type'] = ValueArgument(\
427
            'change object\'s content type', '--content-type')
428
        self.arguments['delimiter'] = DelimiterArgument(self,
429
            parsed_name='--delimiter',
430
            help=u'copy objects prefixed as src_object + delimiter')
431
        self.arguments['recursive'] = FlagArgument(
432
            'mass copy with delimiter /', ('-r', '--recursive'))
433

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

    
452

    
453
@command(pithos_cmds)
454
class store_move(_store_container_command):
455
    """Copy an object"""
456

    
457
    def __init__(self, arguments={}):
458
        super(self.__class__, self).__init__(arguments)
459

    
460
        self.arguments['source_version'] = ValueArgument(\
461
            'copy specific version', '--source-version')
462
        self.arguments['public'] = FlagArgument(\
463
            'make object publicly accessible', '--public')
464
        self.arguments['content_type'] = ValueArgument(\
465
            'change object\'s content type', '--content-type')
466
        self.arguments['delimiter'] = DelimiterArgument(self,
467
            parsed_name='--delimiter',
468
            help='move objects prefixed as src_object + delimiter')
469
        self.arguments['recursive'] = FlagArgument(\
470
            'copy with delimiter /', ('-r', '--recursive'))
471

    
472
    def main(self, source_container___path, destination_container____path__):
473
        super(self.__class__,
474
            self).main(source_container___path, path_is_optional=False)
475
        try:
476
            dst = destination_container____path__.split(':')
477
            dst_cont = dst[0]
478
            dst_path = dst[1] if len(dst) > 1 else False
479
            self.client.move_object(src_container=self.container,
480
                src_object=self.path,
481
                dst_container=dst_cont,
482
                dst_object=dst_path,
483
                source_version=self.get_argument('source_version'),
484
                public=self.get_argument('public'),
485
                content_type=self.get_argument('content_type'),
486
                delimiter=self.get_argument('delimiter'))
487
        except ClientError as err:
488
            raiseCLIError(err)
489

    
490

    
491
@command(pithos_cmds)
492
class store_append(_store_container_command):
493
    """Append local file to (existing) remote object"""
494

    
495
    def __init__(self, arguments={}):
496
        super(self.__class__, self).__init__(arguments)
497
        self.arguments['progress_bar'] = ProgressBarArgument(\
498
            'do not show progress bar', '--no-progress-bar', False)
499

    
500
    def main(self, local_path, container___path):
501
        super(self.__class__,
502
            self).main(container___path, path_is_optional=False)
503
        try:
504
            f = open(local_path, 'r')
505
            progress_bar = self.arguments['progress_bar']
506
            try:
507
                upload_cb = progress_bar.get_generator('Appending blocks')
508
            except Exception:
509
                upload_cb = None
510
            self.client.append_object(object=self.path,
511
                source_file=f,
512
                upload_cb=upload_cb)
513
        except ClientError as err:
514
            progress_bar.finish()
515
            raiseCLIError(err)
516
        finally:
517
            progress_bar.finish()
518

    
519

    
520
@command(pithos_cmds)
521
class store_truncate(_store_container_command):
522
    """Truncate remote file up to a size"""
523

    
524
    def main(self, container___path, size=0):
525
        super(self.__class__,
526
            self).main(container___path, path_is_optional=False)
527
        try:
528
            self.client.truncate_object(self.path, size)
529
        except ClientError as err:
530
            raiseCLIError(err)
531

    
532

    
533
@command(pithos_cmds)
534
class store_overwrite(_store_container_command):
535
    """Overwrite part (from start to end) of a remote file"""
536

    
537
    def __init__(self, arguments={}):
538
        super(self.__class__, self).__init__(arguments)
539
        self.arguments['progress_bar'] = ProgressBarArgument(\
540
            'do not show progress bar', '--no-progress-bar', False)
541

    
542
    def main(self, local_path, container___path, start, end):
543
        super(self.__class__,
544
            self).main(container___path, path_is_optional=False)
545
        try:
546
            f = open(local_path, 'r')
547
            progress_bar = self.arguments['progress_bar']
548
            try:
549
                upload_cb = progress_bar.get_generator('Overwritting blocks')
550
            except Exception:
551
                upload_cb = None
552
            self.client.overwrite_object(object=self.path,
553
                start=start,
554
                end=end,
555
                source_file=f,
556
                upload_cb=upload_cb)
557
        except ClientError as err:
558
            progress_bar.finish()
559
            raiseCLIError(err)
560
        finally:
561
            progress_bar.finish()
562

    
563

    
564
@command(pithos_cmds)
565
class store_manifest(_store_container_command):
566
    """Create a remote file with uploaded parts by manifestation"""
567

    
568
    def __init__(self, arguments={}):
569
        super(self.__class__, self).__init__(arguments)
570
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
571
        self.arguments['content_encoding'] = ValueArgument(\
572
            'provide the object MIME content type', '--content-encoding')
573
        self.arguments['content_disposition'] = ValueArgument(\
574
            'provide the presentation style of the object',
575
            '--content-disposition')
576
        self.arguments['content_type'] = ValueArgument(\
577
            'create object with specific content type', '--content-type')
578
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
579
            help='define object sharing policy ' +\
580
            '( "read=user1,grp1,user2,... write=user1,grp2,..." )')
581
        self.arguments['public'] = FlagArgument(\
582
            'make object publicly accessible', '--public')
583

    
584
    def main(self, container___path):
585
        super(self.__class__,
586
            self).main(container___path, path_is_optional=False)
587
        try:
588
            self.client.create_object_by_manifestation(self.path,
589
                content_encoding=self.get_argument('content_encoding'),
590
                content_disposition=self.get_argument('content_disposition'),
591
                content_type=self.get_argument('content_type'),
592
                sharing=self.get_argument('sharing'),
593
                public=self.get_argument('public'))
594
        except ClientError as err:
595
            raiseCLIError(err)
596

    
597

    
598
@command(pithos_cmds)
599
class store_upload(_store_container_command):
600
    """Upload a file"""
601

    
602
    def __init__(self, arguments={}):
603
        super(self.__class__, self).__init__(arguments)
604
        self.arguments['use_hashes'] = FlagArgument(\
605
            'provide hashmap file instead of data', '--use-hashes')
606
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
607
        self.arguments['unchunked'] = FlagArgument(\
608
            'avoid chunked transfer mode', '--unchunked')
609
        self.arguments['content_encoding'] = ValueArgument(\
610
            'provide the object MIME content type', '--content-encoding')
611
        self.arguments['content_disposition'] = ValueArgument(\
612
            'provide the presentation style of the object',
613
            '--content-disposition')
614
        self.arguments['content_type'] = ValueArgument(\
615
            'create object with specific content type', '--content-type')
616
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
617
            help='define sharing object policy ' +\
618
            '( "read=user1,grp1,user2,... write=user1,grp2,...')
619
        self.arguments['public'] = FlagArgument(\
620
            'make object publicly accessible', '--public')
621
        self.arguments['poolsize'] = IntArgument(\
622
            'set pool size', '--with-pool-size')
623
        self.arguments['progress_bar'] = ProgressBarArgument(\
624
            'do not show progress bar', '--no-progress-bar', False)
625

    
626
    def main(self, local_path, container____path__):
627
        super(self.__class__, self).main(container____path__)
628
        remote_path = local_path if self.path is None else self.path
629
        poolsize = self.get_argument('poolsize')
630
        if poolsize is not None:
631
            self.client.POOL_SIZE = int(poolsize)
632
        params = dict(content_encoding=self.get_argument('content_encoding'),
633
            content_type=self.get_argument('content_type'),
634
            content_disposition=self.get_argument('content_disposition'),
635
            sharing=self.get_argument('sharing'),
636
            public=self.get_argument('public'))
637
        try:
638
            progress_bar = self.arguments['progress_bar']
639
            hash_bar = progress_bar.clone()
640
            with open(local_path) as f:
641
                if self.get_argument('unchunked'):
642
                    self.client.upload_object_unchunked(remote_path, f,
643
                    etag=self.get_argument('etag'),
644
                    withHashFile=self.get_argument('use_hashes'),
645
                    **params)
646
                else:
647
                    hash_cb = hash_bar.get_generator(\
648
                        'Calculating block hashes')
649
                    upload_cb = progress_bar.get_generator('Uploading')
650
                    self.client.upload_object(remote_path, f,
651
                        hash_cb=hash_cb,
652
                        upload_cb=upload_cb,
653
                        **params)
654
                    progress_bar.finish()
655
                    hash_bar.finish()
656
        except ClientError as err:
657
            progress_bar.finish()
658
            hash_bar.finish()
659
            raiseCLIError(err)
660
        except IOError as err:
661
            progress_bar.finish()
662
            hash_bar.finish()
663
            raise CLIError(
664
                message='Failed to read form file %s' % local_path,
665
                importance=2,
666
                details=unicode(err))
667
        print 'Upload completed'
668

    
669

    
670
@command(pithos_cmds)
671
class store_cat(_store_container_command):
672
    """Print a file to console"""
673

    
674
    def __init__(self, arguments={}):
675
        super(self.__class__, self).__init__(arguments)
676
        self.arguments['range'] =\
677
            RangeArgument('show range of data', '--range')
678
        self.arguments['if_match'] =\
679
            ValueArgument('show output if ETags match', '--if-match')
680
        self.arguments['if_none_match'] =\
681
            ValueArgument('show output if ETags match', '--if-none-match')
682
        self.arguments['if_modified_since'] =\
683
            DateArgument('show output modified since then',
684
            '--if-modified-since')
685
        self.arguments['if_unmodified_since'] =\
686
            DateArgument('show output unmodified since then',
687
            '--if-unmodified-since')
688
        self.arguments['object_version'] =\
689
            ValueArgument('get the specific version', '--object-version')
690

    
691
    def main(self, container___path):
692
        super(self.__class__,
693
            self).main(container___path, path_is_optional=False)
694
        try:
695
            self.client.download_object(self.path, stdout,
696
            range=self.get_argument('range'),
697
            version=self.get_argument('object_version'),
698
            if_match=self.get_argument('if_match'),
699
            if_none_match=self.get_argument('if_none_match'),
700
            if_modified_since=self.get_argument('if_modified_since'),
701
            if_unmodified_since=self.get_argument('if_unmodified_since'))
702
        except ClientError as err:
703
            raiseCLIError(err)
704

    
705

    
706
@command(pithos_cmds)
707
class store_download(_store_container_command):
708
    """Download a file"""
709

    
710
    def __init__(self, arguments={}):
711
        super(self.__class__, self).__init__(arguments)
712
        self.arguments['resume'] = FlagArgument(parsed_name='--resume',
713
            help='Resume a previous download instead of overwritting it')
714
        self.arguments['range'] = RangeArgument(\
715
            'show range of data', '--range')
716
        self.arguments['if_match'] = ValueArgument(\
717
            'show output if ETags match', '--if-match')
718
        self.arguments['if_none_match'] = ValueArgument(\
719
            'show output if ETags match', '--if-none-match')
720
        self.arguments['if_modified_since'] = DateArgument(\
721
            'show output modified since then', '--if-modified-since')
722
        self.arguments['if_unmodified_since'] = DateArgument(\
723
            'show output unmodified since then', '--if-unmodified-since')
724
        self.arguments['object_version'] = ValueArgument(\
725
            'get the specific version', '--object-version')
726
        self.arguments['poolsize'] = IntArgument(\
727
            'set pool size', '--with-pool-size')
728
        self.arguments['progress_bar'] = ProgressBarArgument(\
729
            'do not show progress bar', '--no-progress-bar', False)
730

    
731
    def main(self, container___path, local_path):
732
        super(self.__class__,
733
            self).main(container___path, path_is_optional=False)
734

    
735
        # setup output stream
736
        if local_path is None:
737
            out = stdout
738
        else:
739
            try:
740
                if self.get_argument('resume'):
741
                    out = open(local_path, 'rwb+')
742
                else:
743
                    out = open(local_path, 'wb+')
744
            except IOError as err:
745
                raise CLIError(message='Cannot write to file %s - %s'\
746
                    % (local_path, unicode(err)),
747
                    importance=1)
748
        poolsize = self.get_argument('poolsize')
749
        if poolsize is not None:
750
            self.client.POOL_SIZE = int(poolsize)
751

    
752
        try:
753
            progress_bar = self.arguments['progress_bar']
754
            download_cb = progress_bar.get_generator('Downloading')
755
            self.client.download_object(self.path, out,
756
                download_cb=download_cb,
757
                range=self.get_argument('range'),
758
                version=self.get_argument('object_version'),
759
                if_match=self.get_argument('if_match'),
760
                resume=self.get_argument('resume'),
761
                if_none_match=self.get_argument('if_none_match'),
762
                if_modified_since=self.get_argument('if_modified_since'),
763
                if_unmodified_since=self.get_argument('if_unmodified_since'))
764
            progress_bar.finish()
765
        except ClientError as err:
766
            progress_bar.finish()
767
            raiseCLIError(err)
768
        except KeyboardInterrupt:
769
            from threading import enumerate as activethreads
770
            stdout.write('\nFinishing active threads ')
771
            for thread in activethreads():
772
                stdout.flush()
773
                try:
774
                    thread.join()
775
                    stdout.write('.')
776
                except RuntimeError:
777
                    continue
778
            progress_bar.finish()
779
            print('\ndownload canceled by user')
780
            if local_path is not None:
781
                print('to resume, re-run with --resume')
782
        except Exception as e:
783
            progress_bar.finish()
784
            raise e
785
        print
786

    
787

    
788
@command(pithos_cmds)
789
class store_hashmap(_store_container_command):
790
    """Get the hashmap of an object"""
791

    
792
    def __init__(self, arguments={}):
793
        super(self.__class__, self).__init__(arguments)
794
        self.arguments['if_match'] =\
795
            ValueArgument('show output if ETags match', '--if-match')
796
        self.arguments['if_none_match'] =\
797
            ValueArgument('show output if ETags match', '--if-none-match')
798
        self.arguments['if_modified_since'] =\
799
            DateArgument('show output modified since then',
800
            '--if-modified-since')
801
        self.arguments['if_unmodified_since'] =\
802
            DateArgument('show output unmodified since then',
803
            '--if-unmodified-since')
804
        self.arguments['object_version'] =\
805
            ValueArgument('get the specific version', '--object-version')
806

    
807
    def main(self, container___path):
808
        super(self.__class__,
809
            self).main(container___path, path_is_optional=False)
810
        try:
811
            data = self.client.get_object_hashmap(self.path,
812
                version=self.arguments('object_version'),
813
                if_match=self.arguments('if_match'),
814
                if_none_match=self.arguments('if_none_match'),
815
                if_modified_since=self.arguments('if_modified_since'),
816
                if_unmodified_since=self.arguments('if_unmodified_since'))
817
        except ClientError as err:
818
            raiseCLIError(err)
819
        print_dict(data)
820

    
821

    
822
@command(pithos_cmds)
823
class store_delete(_store_container_command):
824
    """Delete a container [or an object]"""
825

    
826
    def __init__(self, arguments={}):
827
        super(self.__class__, self).__init__(arguments)
828
        self.arguments['until'] = DateArgument(\
829
            'remove history until that date', '--until')
830
        self.arguments['recursive'] = FlagArgument(\
831
            'empty dir or container and delete (if dir)',
832
            ('-r', '--recursive'))
833
        self.arguments['delimiter'] = DelimiterArgument(self,
834
            parsed_name='--delimiter',
835
            help='delete objects prefixed with <object><delimiter>')
836

    
837
    def main(self, container____path__):
838
        super(self.__class__, self).main(container____path__)
839
        try:
840
            if self.path is None:
841
                self.client.del_container(until=self.get_argument('until'),
842
                    delimiter=self.get_argument('delimiter'))
843
            else:
844
                # self.client.delete_object(self.path)
845
                self.client.del_object(self.path,
846
                    until=self.get_argument('until'),
847
                    delimiter=self.get_argument('delimiter'))
848
        except ClientError as err:
849
            raiseCLIError(err)
850

    
851

    
852
@command(pithos_cmds)
853
class store_purge(_store_container_command):
854
    """Purge a container"""
855

    
856
    def main(self, container):
857
        super(self.__class__, self).main(container)
858
        try:
859
            self.client.purge_container()
860
        except ClientError as err:
861
            raiseCLIError(err)
862

    
863

    
864
@command(pithos_cmds)
865
class store_publish(_store_container_command):
866
    """Publish an object"""
867

    
868
    def main(self, container___path):
869
        super(self.__class__,
870
            self).main(container___path, path_is_optional=False)
871
        try:
872
            self.client.publish_object(self.path)
873
        except ClientError as err:
874
            raiseCLIError(err)
875

    
876

    
877
@command(pithos_cmds)
878
class store_unpublish(_store_container_command):
879
    """Unpublish an object"""
880

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

    
889

    
890
@command(pithos_cmds)
891
class store_permissions(_store_container_command):
892
    """Get object read/write permissions """
893

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

    
903

    
904
@command(pithos_cmds)
905
class store_setpermissions(_store_container_command):
906
    """Set sharing permissions """
907

    
908
    def format_permition_dict(self, permissions):
909
        read = False
910
        write = False
911
        for perms in permissions:
912
            splstr = perms.split('=')
913
            if 'read' == splstr[0]:
914
                read = [user_or_group.strip() \
915
                for user_or_group in splstr[1].split(',')]
916
            elif 'write' == splstr[0]:
917
                write = [user_or_group.strip() \
918
                for user_or_group in splstr[1].split(',')]
919
            else:
920
                read = False
921
                write = False
922
        if not read and not write:
923
            raise CLIError(importance=0,
924
                message='Usage:\tread=<groups,users> write=<groups,users>')
925
        return (read, write)
926

    
927
    def main(self, container___path, *permissions):
928
        super(self.__class__,
929
            self).main(container___path, path_is_optional=False)
930
        (read, write) = self.format_permition_dict(permissions)
931
        try:
932
            self.client.set_object_sharing(self.path,
933
                read_permition=read, write_permition=write)
934
        except ClientError as err:
935
            raiseCLIError(err)
936

    
937

    
938
@command(pithos_cmds)
939
class store_delpermissions(_store_container_command):
940
    """Delete all sharing permissions"""
941

    
942
    def main(self, container___path):
943
        super(self.__class__,
944
            self).main(container___path, path_is_optional=False)
945
        try:
946
            self.client.del_object_sharing(self.path)
947
        except ClientError as err:
948
            raiseCLIError(err)
949

    
950

    
951
@command(pithos_cmds)
952
class store_info(_store_container_command):
953
    """Get information for account [, container [or object]]"""
954

    
955
    def __init__(self, arguments={}):
956
        super(self.__class__, self).__init__(arguments)
957
        self.arguments['object_version'] =\
958
            ValueArgument(parsed_name='--object-version',
959
            help='show specific version \ (applies only for objects)')
960

    
961
    def main(self, container____path__=None):
962
        super(self.__class__, self).main(container____path__)
963
        try:
964
            if self.container is None:
965
                reply = self.client.get_account_info()
966
            elif self.path is None:
967
                reply = self.client.get_container_info(self.container)
968
            else:
969
                reply = self.client.get_object_info(self.path,
970
                    version=self.get_argument('object_version'))
971
        except ClientError as err:
972
            raiseCLIError(err)
973
        print_dict(reply)
974

    
975

    
976
@command(pithos_cmds)
977
class store_meta(_store_container_command):
978
    """Get custom meta-content for account [, container [or object]]"""
979

    
980
    def __init__(self, arguments={}):
981
        super(self.__class__, self).__init__(arguments)
982
        self.arguments['detail'] =\
983
            FlagArgument('show detailed output', '-l')
984
        self.arguments['until'] =\
985
            DateArgument('show metadata until then', '--until')
986
        self.arguments['object_version'] =\
987
            ValueArgument(parsed_name='--object-version',
988
            help='show specific version \ (applies only for objects)')
989

    
990
    def main(self, container____path__=None):
991
        super(self.__class__, self).main(container____path__)
992

    
993
        detail = self.get_argument('detail')
994
        try:
995
            until = self.get_argument('until')
996
            if self.container is None:
997
                print(bold(self.client.account))
998
                if detail:
999
                    reply = self.client.get_account_info(until=until)
1000
                else:
1001
                    reply = self.client.get_account_meta(until=until)
1002
                    reply = pretty_keys(reply, '-')
1003
            elif self.path is None:
1004
                print(bold('%s: %s' % (self.client.account, self.container)))
1005
                if detail:
1006
                    reply = self.client.get_container_info(until=until)
1007
                else:
1008
                    cmeta = self.client.get_container_meta(until=until)
1009
                    ometa = self.client.get_container_object_meta(until=until)
1010
                    reply = {'container-meta': pretty_keys(cmeta, '-'),
1011
                        'object-meta': pretty_keys(ometa, '-')}
1012
            else:
1013
                print(bold('%s: %s:%s'\
1014
                    % (self.client.account, self.container, self.path)))
1015
                version = self.get_argument('object_version')
1016
                if detail:
1017
                    reply = self.client.get_object_info(self.path,
1018
                        version=version)
1019
                else:
1020
                    reply = self.client.get_object_meta(self.path,
1021
                        version=version)
1022
                    reply = pretty_keys(pretty_keys(reply, '-'))
1023
        except ClientError as err:
1024
            raiseCLIError(err)
1025
        print_dict(reply)
1026

    
1027

    
1028
@command(pithos_cmds)
1029
class store_setmeta(_store_container_command):
1030
    """Set a new metadatum for account [, container [or object]]"""
1031

    
1032
    def main(self, metakey___metaval, container____path__=None):
1033
        super(self.__class__, self).main(container____path__)
1034
        try:
1035
            metakey, metavalue = metakey___metaval.split(':')
1036
        except ValueError:
1037
            raise CLIError(message='Usage:  metakey:metavalue', importance=1)
1038
        try:
1039
            if self.container is None:
1040
                self.client.set_account_meta({metakey: metavalue})
1041
            elif self.path is None:
1042
                self.client.set_container_meta({metakey: metavalue})
1043
            else:
1044
                self.client.set_object_meta(self.path, {metakey: metavalue})
1045
        except ClientError as err:
1046
            raiseCLIError(err)
1047

    
1048

    
1049
@command(pithos_cmds)
1050
class store_delmeta(_store_container_command):
1051
    """Delete an existing metadatum of account [, container [or object]]"""
1052

    
1053
    def main(self, metakey, container____path__=None):
1054
        super(self.__class__, self).main(container____path__)
1055
        try:
1056
            if self.container is None:
1057
                self.client.del_account_meta(metakey)
1058
            elif self.path is None:
1059
                self.client.del_container_meta(metakey)
1060
            else:
1061
                self.client.del_object_meta(metakey, self.path)
1062
        except ClientError as err:
1063
            raiseCLIError(err)
1064

    
1065

    
1066
@command(pithos_cmds)
1067
class store_quota(_store_account_command):
1068
    """Get  quota for account [or container]"""
1069

    
1070
    def main(self, container=None):
1071
        super(self.__class__, self).main()
1072
        try:
1073
            if container is None:
1074
                reply = self.client.get_account_quota()
1075
            else:
1076
                reply = self.client.get_container_quota(container)
1077
        except ClientError as err:
1078
            raiseCLIError(err)
1079
        print_dict(reply)
1080

    
1081

    
1082
@command(pithos_cmds)
1083
class store_setquota(_store_account_command):
1084
    """Set new quota (in KB) for account [or container]"""
1085

    
1086
    def main(self, quota, container=None):
1087
        super(self.__class__, self).main()
1088
        try:
1089
            if container is None:
1090
                self.client.set_account_quota(quota)
1091
            else:
1092
                self.client.container = container
1093
                self.client.set_container_quota(quota)
1094
        except ClientError as err:
1095
            raiseCLIError(err)
1096

    
1097

    
1098
@command(pithos_cmds)
1099
class store_versioning(_store_account_command):
1100
    """Get  versioning for account [or container ]"""
1101

    
1102
    def main(self, container=None):
1103
        super(self.__class__, self).main()
1104
        try:
1105
            if container is None:
1106
                reply = self.client.get_account_versioning()
1107
            else:
1108
                reply = self.client.get_container_versioning(container)
1109
        except ClientError as err:
1110
            raiseCLIError(err)
1111
        print_dict(reply)
1112

    
1113

    
1114
@command(pithos_cmds)
1115
class store_setversioning(_store_account_command):
1116
    """Set new versioning (auto, none) for account [or container]"""
1117

    
1118
    def main(self, versioning, container=None):
1119
        super(self.__class__, self).main()
1120
        try:
1121
            if container is None:
1122
                self.client.set_account_versioning(versioning)
1123
            else:
1124
                self.client.container = container
1125
                self.client.set_container_versioning(versioning)
1126
        except ClientError as err:
1127
            raiseCLIError(err)
1128

    
1129

    
1130
@command(pithos_cmds)
1131
class store_group(_store_account_command):
1132
    """Get user groups details for account"""
1133

    
1134
    def main(self):
1135
        super(self.__class__, self).main()
1136
        try:
1137
            reply = self.client.get_account_group()
1138
        except ClientError as err:
1139
            raiseCLIError(err)
1140
        print_dict(reply)
1141

    
1142

    
1143
@command(pithos_cmds)
1144
class store_setgroup(_store_account_command):
1145
    """Create/update a new user group on account"""
1146

    
1147
    def main(self, groupname, *users):
1148
        super(self.__class__, self).main()
1149
        try:
1150
            self.client.set_account_group(groupname, users)
1151
        except ClientError as err:
1152
            raiseCLIError(err)
1153

    
1154

    
1155
@command(pithos_cmds)
1156
class store_delgroup(_store_account_command):
1157
    """Delete a user group on an account"""
1158

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

    
1166

    
1167
@command(pithos_cmds)
1168
class store_sharers(_store_account_command):
1169
    """List the accounts that share objects with default account"""
1170

    
1171
    def __init__(self, arguments={}):
1172
        super(self.__class__, self).__init__(arguments)
1173
        self.arguments['detail'] =\
1174
            FlagArgument('show detailed output', '-l')
1175
        self.arguments['limit'] =\
1176
            IntArgument('show limited output', '--n', default=1000)
1177
        self.arguments['marker'] =\
1178
            ValueArgument('show output greater then marker', '--marker')
1179

    
1180
    def main(self):
1181
        super(self.__class__, self).main()
1182
        try:
1183
            marker = self.get_argument('marker')
1184
            accounts = self.client.get_sharing_accounts(marker=marker)
1185
        except ClientError as err:
1186
            raiseCLIError(err)
1187

    
1188
        for acc in accounts:
1189
            stdout.write(bold(acc['name']) + ' ')
1190
            if self.get_argument('detail'):
1191
                print_dict(acc, exclude='name', ident=4)
1192
        if not self.get_argument('detail'):
1193
            print
1194

    
1195

    
1196
@command(pithos_cmds)
1197
class store_versions(_store_container_command):
1198
    """Get the version list of an object"""
1199

    
1200
    def main(self, container___path):
1201
        super(store_versions, self).main(container___path)
1202
        try:
1203
            versions = self.client.get_object_versionlist(self.path)
1204
        except ClientError as err:
1205
            raise CLIError(err)
1206

    
1207
        print('%s:%s versions' % (self.container, self.path))
1208
        for vitem in versions:
1209
            t = localtime(float(vitem[1]))
1210
            vid = bold(unicode(vitem[0]))
1211
            print('\t%s \t(%s)' % (vid, strftime('%d-%m-%Y %H:%M:%S', t)))