Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (47.5 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, CLISyntaxError
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
from logging import getLogger
48

    
49
kloger = getLogger('kamaki')
50

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

    
54

    
55
# Argument functionality
56

    
57

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

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

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

    
74
    @value.setter
75
    def value(self, newvalue):
76
        self._value = newvalue
77

    
78

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

82
    :value type: "read=term1,term2,... write=term1,term2,..."
83

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

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

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

    
118

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

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

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

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

    
140

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

145
    :value returns: same date in first of DATE_FORMATS
146
    """
147

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

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

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

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

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

    
177

    
178
# Command specs
179

    
180

    
181
class _pithos_init(_command_init):
182
    """Initialize a pithos+ kamaki client"""
183

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

    
198

    
199
class _store_account_command(_pithos_init):
200
    """Base class for account level storage commands"""
201

    
202
    def __init__(self, arguments={}):
203
        super(_store_account_command, self).__init__(arguments)
204
        self.arguments['account'] =\
205
            ValueArgument('Set user account (not permanent)', '--account')
206

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

    
212

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

    
216
    generic_err_details = ['Choose one of the following options:',
217
    '  1. Set store.container variable (permanent)',
218
    '     /config set store.container <container>',
219
    '  2. --container=<container> (temporary, overrides 1)',
220
    '  3. Use <container>:<path> (temporary, overrides all)']
221

    
222
    def __init__(self, arguments={}):
223
        super(_store_container_command, self).__init__(arguments)
224
        self.arguments['container'] =\
225
            ValueArgument('Set container to work with (temporary)',
226
                '--container')
227
        self.container = None
228
        self.path = None
229

    
230
    def extract_container_and_path(self,
231
        container_with_path,
232
        path_is_optional=True):
233
        try:
234
            assert isinstance(container_with_path, str)
235
        except AssertionError as err:
236
            raiseCLIError(err)
237

    
238
        cont, sep, path = container_with_path.partition(':')
239

    
240
        if sep:
241
            if not cont:
242
                raiseCLIError(CLISyntaxError('Container is missing\n',
243
                    details=self.generic_err_details))
244
            alt_cont = self.get_argument('container')
245
            if alt_cont and cont != alt_cont:
246
                raiseCLIError(CLISyntaxError(
247
                    'Conflict: 2 containers (%s, %s)' % (cont, alt_cont),
248
                    details=self.generic_err_details))
249
            self.container = cont
250
            if not path:
251
                raiseCLIError(CLISyntaxError(
252
                    'Path is missing for object in container %s' % cont,
253
                    details=self.generic_err_details))
254
            self.path = path
255
        else:
256
            alt_cont = self.get_argument('container') or self.client.container
257
            if alt_cont:
258
                self.container = alt_cont
259
                self.path = cont
260
            elif path_is_optional:
261
                self.container = cont
262
                self.path = None
263
            else:
264
                self.container = cont
265
                raiseCLIError(CLISyntaxError(
266
                    'Both container and path are required',
267
                    details=self.generic_err_details))
268

    
269
    def main(self, container_with_path=None, path_is_optional=True):
270
        super(_store_container_command, self).main()
271
        if container_with_path is not None:
272
            self.extract_container_and_path(container_with_path,
273
                path_is_optional)
274
            self.client.container = self.container
275
        elif self.get_argument('container') is not None:
276
            self.client.container = self.get_argument('container')
277
        self.container = self.client.container
278

    
279

    
280
@command(pithos_cmds)
281
class store_list(_store_container_command):
282
    """List containers, object trees or objects in a directory
283
    """
284

    
285
    def __init__(self, arguments={}):
286
        super(store_list, self).__init__(arguments)
287
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
288
        self.arguments['show_size'] =\
289
            ValueArgument('print output in chunks of size N', '-N')
290
        self.arguments['limit'] = IntArgument('show limited output', '-n')
291
        self.arguments['marker'] =\
292
            ValueArgument('show output greater that marker', '--marker')
293
        self.arguments['prefix'] =\
294
            ValueArgument('show output staritng with prefix', '--prefix')
295
        self.arguments['delimiter'] =\
296
            ValueArgument('show output up to delimiter', '--delimiter')
297
        self.arguments['path'] =\
298
            ValueArgument('show output starting with prefix up to /', '--path')
299
        self.arguments['meta'] =\
300
            ValueArgument('show output haviung the specified meta keys',
301
            '--meta', default=[])
302
        self.arguments['if_modified_since'] =\
303
            ValueArgument('show output modified since then',
304
                '--if-modified-since')
305
        self.arguments['if_unmodified_since'] =\
306
            ValueArgument('show output not modified since then',
307
            '--if-unmodified-since')
308
        self.arguments['until'] =\
309
            DateArgument('show metadata until then', '--until')
310
        self.arguments['format'] =\
311
            ValueArgument('format to parse until data (default: d/m/Y H:M:S',
312
            '--format')
313
        self.arguments['shared'] = FlagArgument('show only shared', '--shared')
314
        self.arguments['public'] = FlagArgument('show only public', '--public')
315

    
316
    def print_objects(self, object_list):
317
        import sys
318
        try:
319
            limit = self.get_argument('show_size')
320
            limit = int(limit)
321
        except (AttributeError, TypeError):
322
            limit = len(object_list) + 1
323
        for index, obj in enumerate(object_list):
324
            if 'content_type' not in obj:
325
                continue
326
            pretty_obj = obj.copy()
327
            index += 1
328
            empty_space = ' ' * (len(str(len(object_list))) - len(str(index)))
329
            if obj['content_type'] == 'application/directory':
330
                isDir = True
331
                size = 'D'
332
            else:
333
                isDir = False
334
                size = format_size(obj['bytes'])
335
                pretty_obj['bytes'] = '%s (%s)' % (obj['bytes'], size)
336
            oname = bold(obj['name'])
337
            if self.get_argument('detail'):
338
                print('%s%s. %s' % (empty_space, index, oname))
339
                print_dict(pretty_keys(pretty_obj), exclude=('name'))
340
                print
341
            else:
342
                oname = '%s%s. %6s %s' % (empty_space, index, size, oname)
343
                oname += '/' if isDir else ''
344
                print(oname)
345
            if limit <= index < len(object_list) and index % limit == 0:
346
                print('(press "enter" to continue)')
347
                sys.stdin.read(1)
348

    
349
    def print_containers(self, container_list):
350
        import sys
351
        try:
352
            limit = self.get_argument('show_size')
353
            limit = int(limit)
354
        except (AttributeError, TypeError):
355
            limit = len(container_list) + 1
356
        for index, container in enumerate(container_list):
357
            if 'bytes' in container:
358
                size = format_size(container['bytes'])
359
            cname = '%s. %s' % (index + 1, bold(container['name']))
360
            if self.get_argument('detail'):
361
                print(cname)
362
                pretty_c = container.copy()
363
                if 'bytes' in container:
364
                    pretty_c['bytes'] = '%s (%s)' % (container['bytes'], size)
365
                print_dict(pretty_keys(pretty_c), exclude=('name'))
366
                print
367
            else:
368
                if 'count' in container and 'bytes' in container:
369
                    print('%s (%s, %s objects)'\
370
                    % (cname, size, container['count']))
371
                else:
372
                    print(cname)
373
            if limit <= index < len(container_list) and index % limit == 0:
374
                print('(press "enter" to continue)')
375
                sys.stdin.read(1)
376

    
377
    def main(self, container____path__=None):
378
        super(self.__class__, self).main(container____path__)
379
        try:
380
            if self.container is None:
381
                r = self.client.account_get(limit=self.get_argument('limit'),
382
                    marker=self.get_argument('marker'),
383
                    if_modified_since=self.get_argument('if_modified_since'),
384
                    if_unmodified_since=self.get_argument(\
385
                        'if_unmodified_since'),
386
                    until=self.get_argument('until'),
387
                    show_only_shared=self.get_argument('shared'))
388
                self.print_containers(r.json)
389
            else:
390
                prefix = self.path if self.path\
391
                else self.get_argument('prefix')
392
                r = self.client.container_get(limit=self.get_argument('limit'),
393
                    marker=self.get_argument('marker'),
394
                    prefix=prefix,
395
                    delimiter=self.get_argument('delimiter'),
396
                    path=self.get_argument('path'),
397
                    if_modified_since=self.get_argument('if_modified_since'),
398
                    if_unmodified_since=self.get_argument(\
399
                        'if_unmodified_since'),
400
                    until=self.get_argument('until'),
401
                    meta=self.get_argument('meta'),
402
                    show_only_shared=self.get_argument('shared'))
403
                self.print_objects(r.json)
404
        except ClientError as err:
405
            if err.status == 404:
406
                if 'Container does not exist' in ('%s' % err):
407
                    raiseCLIError(err, 'No container %s in account %s'\
408
                        % (self.container, self.account),
409
                        details=self.generic_err_details)
410
                elif 'Object does not exist' in ('%s' % err):
411
                    raiseCLIError(err, 'No object %s in %s\'s container %s'\
412
                        % (self.path, self.account, self.container),
413
                        details=self.generic_err_details)
414
            raiseCLIError(err)
415

    
416

    
417
@command(pithos_cmds)
418
class store_mkdir(_store_container_command):
419
    """Create a directory"""
420

    
421
    def main(self, container___directory):
422
        super(self.__class__,
423
            self).main(container___directory, path_is_optional=False)
424
        try:
425
            self.client.create_directory(self.path)
426
        except ClientError as err:
427
            raiseCLIError(err)
428

    
429

    
430
@command(pithos_cmds)
431
class store_create(_store_container_command):
432
    """Create a container or a directory object"""
433

    
434
    def __init__(self, arguments={}):
435
        super(self.__class__, self).__init__(arguments)
436
        self.arguments['versioning'] = \
437
            ValueArgument('set container versioning (auto/none)',
438
            '--versioning')
439
        self.arguments['quota'] =\
440
            IntArgument('set default container quota', '--quota')
441
        self.arguments['meta'] =\
442
            KeyValueArgument(
443
                'set container metadata (can be repeated)', '--meta')
444
            #  MetaArgument('set container metadata', '--meta')
445

    
446
    def main(self, container____directory__):
447
        super(self.__class__, self).main(container____directory__)
448
        try:
449
            if self.path is None:
450
                self.client.container_put(quota=self.get_argument('quota'),
451
                    versioning=self.get_argument('versioning'),
452
                    metadata=self.get_argument('meta'))
453
            else:
454
                self.client.create_directory(self.path)
455
        except ClientError as err:
456
            raiseCLIError(err)
457

    
458

    
459
@command(pithos_cmds)
460
class store_copy(_store_container_command):
461
    """Copy an object"""
462

    
463
    def __init__(self, arguments={}):
464
        super(self.__class__, self).__init__(arguments)
465
        self.arguments['source_version'] = ValueArgument(\
466
            'copy specific version', '--source-version')
467
        self.arguments['public'] = ValueArgument(\
468
            'make object publicly accessible', '--public')
469
        self.arguments['content_type'] = ValueArgument(\
470
            'change object\'s content type', '--content-type')
471
        self.arguments['delimiter'] = DelimiterArgument(self,
472
            parsed_name='--delimiter',
473
            help=u'copy objects prefixed as src_object + delimiter')
474
        self.arguments['recursive'] = FlagArgument(
475
            'mass copy with delimiter /', ('-r', '--recursive'))
476

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

    
495

    
496
@command(pithos_cmds)
497
class store_move(_store_container_command):
498
    """Copy an object"""
499

    
500
    def __init__(self, arguments={}):
501
        super(self.__class__, self).__init__(arguments)
502

    
503
        self.arguments['source_version'] = ValueArgument(\
504
            'copy specific version', '--source-version')
505
        self.arguments['public'] = FlagArgument(\
506
            'make object publicly accessible', '--public')
507
        self.arguments['content_type'] = ValueArgument(\
508
            'change object\'s content type', '--content-type')
509
        self.arguments['delimiter'] = DelimiterArgument(self,
510
            parsed_name='--delimiter',
511
            help='move objects prefixed as src_object + delimiter')
512
        self.arguments['recursive'] = FlagArgument(\
513
            'copy with delimiter /', ('-r', '--recursive'))
514

    
515
    def main(self, source_container___path, destination_container____path__):
516
        super(self.__class__,
517
            self).main(source_container___path, path_is_optional=False)
518
        try:
519
            dst = destination_container____path__.split(':')
520
            dst_cont = dst[0]
521
            dst_path = dst[1] if len(dst) > 1 else False
522
            self.client.move_object(src_container=self.container,
523
                src_object=self.path,
524
                dst_container=dst_cont,
525
                dst_object=dst_path,
526
                source_version=self.get_argument('source_version'),
527
                public=self.get_argument('public'),
528
                content_type=self.get_argument('content_type'),
529
                delimiter=self.get_argument('delimiter'))
530
        except ClientError as err:
531
            raiseCLIError(err)
532

    
533

    
534
@command(pithos_cmds)
535
class store_append(_store_container_command):
536
    """Append local file to (existing) remote object"""
537

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

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

    
562

    
563
@command(pithos_cmds)
564
class store_truncate(_store_container_command):
565
    """Truncate remote file up to a size"""
566

    
567
    def main(self, container___path, size=0):
568
        super(self.__class__,
569
            self).main(container___path, path_is_optional=False)
570
        try:
571
            self.client.truncate_object(self.path, size)
572
        except ClientError as err:
573
            raiseCLIError(err)
574

    
575

    
576
@command(pithos_cmds)
577
class store_overwrite(_store_container_command):
578
    """Overwrite part (from start to end) of a remote file"""
579

    
580
    def __init__(self, arguments={}):
581
        super(self.__class__, self).__init__(arguments)
582
        self.arguments['progress_bar'] = ProgressBarArgument(\
583
            'do not show progress bar', '--no-progress-bar', False)
584

    
585
    def main(self, local_path, container___path, start, end):
586
        super(self.__class__,
587
            self).main(container___path, path_is_optional=False)
588
        try:
589
            f = open(local_path, 'rb')
590
            progress_bar = self.arguments['progress_bar']
591
            try:
592
                upload_cb = progress_bar.get_generator('Overwritting blocks')
593
            except Exception:
594
                upload_cb = None
595
            self.client.overwrite_object(object=self.path,
596
                start=start,
597
                end=end,
598
                source_file=f,
599
                upload_cb=upload_cb)
600
        except ClientError as err:
601
            progress_bar.finish()
602
            raiseCLIError(err)
603
        finally:
604
            progress_bar.finish()
605

    
606

    
607
@command(pithos_cmds)
608
class store_manifest(_store_container_command):
609
    """Create a remote file with uploaded parts by manifestation"""
610

    
611
    def __init__(self, arguments={}):
612
        super(self.__class__, self).__init__(arguments)
613
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
614
        self.arguments['content_encoding'] = ValueArgument(\
615
            'provide the object MIME content type', '--content-encoding')
616
        self.arguments['content_disposition'] = ValueArgument(\
617
            'provide the presentation style of the object',
618
            '--content-disposition')
619
        self.arguments['content_type'] = ValueArgument(\
620
            'create object with specific content type', '--content-type')
621
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
622
            help='define object sharing policy ' +\
623
            '( "read=user1,grp1,user2,... write=user1,grp2,..." )')
624
        self.arguments['public'] = FlagArgument(\
625
            'make object publicly accessible', '--public')
626

    
627
    def main(self, container___path):
628
        super(self.__class__,
629
            self).main(container___path, path_is_optional=False)
630
        try:
631
            self.client.create_object_by_manifestation(self.path,
632
                content_encoding=self.get_argument('content_encoding'),
633
                content_disposition=self.get_argument('content_disposition'),
634
                content_type=self.get_argument('content_type'),
635
                sharing=self.get_argument('sharing'),
636
                public=self.get_argument('public'))
637
        except ClientError as err:
638
            raiseCLIError(err)
639

    
640

    
641
@command(pithos_cmds)
642
class store_upload(_store_container_command):
643
    """Upload a file"""
644

    
645
    def __init__(self, arguments={}):
646
        super(self.__class__, self).__init__(arguments)
647
        self.arguments['use_hashes'] = FlagArgument(\
648
            'provide hashmap file instead of data', '--use-hashes')
649
        self.arguments['etag'] = ValueArgument('check written data', '--etag')
650
        self.arguments['unchunked'] = FlagArgument(\
651
            'avoid chunked transfer mode', '--unchunked')
652
        self.arguments['content_encoding'] = ValueArgument(\
653
            'provide the object MIME content type', '--content-encoding')
654
        self.arguments['content_disposition'] = ValueArgument(\
655
            'provide the presentation style of the object',
656
            '--content-disposition')
657
        self.arguments['content_type'] = ValueArgument(\
658
            'create object with specific content type', '--content-type')
659
        self.arguments['sharing'] = SharingArgument(parsed_name='--sharing',
660
            help='define sharing object policy ' +\
661
            '( "read=user1,grp1,user2,... write=user1,grp2,...')
662
        self.arguments['public'] = FlagArgument(\
663
            'make object publicly accessible', '--public')
664
        self.arguments['poolsize'] = IntArgument(\
665
            'set pool size', '--with-pool-size')
666
        self.arguments['progress_bar'] = ProgressBarArgument(\
667
            'do not show progress bar', '--no-progress-bar', False)
668

    
669
    def main(self, local_path, container____path__):
670
        super(self.__class__, self).main(container____path__)
671
        remote_path = local_path if self.path is None else self.path
672
        poolsize = self.get_argument('poolsize')
673
        if poolsize is not None:
674
            self.client.POOL_SIZE = int(poolsize)
675
        params = dict(content_encoding=self.get_argument('content_encoding'),
676
            content_type=self.get_argument('content_type'),
677
            content_disposition=self.get_argument('content_disposition'),
678
            sharing=self.get_argument('sharing'),
679
            public=self.get_argument('public'))
680
        try:
681
            progress_bar = self.arguments['progress_bar']
682
            hash_bar = progress_bar.clone()
683
            with open(local_path, 'rb') as f:
684
                if self.get_argument('unchunked'):
685
                    self.client.upload_object_unchunked(remote_path, f,
686
                    etag=self.get_argument('etag'),
687
                    withHashFile=self.get_argument('use_hashes'),
688
                    **params)
689
                else:
690
                    hash_cb = hash_bar.get_generator(\
691
                        'Calculating block hashes')
692
                    upload_cb = progress_bar.get_generator('Uploading')
693
                    self.client.upload_object(remote_path, f,
694
                        hash_cb=hash_cb,
695
                        upload_cb=upload_cb,
696
                        **params)
697
                    progress_bar.finish()
698
                    hash_bar.finish()
699
        except ClientError as err:
700
            progress_bar.finish()
701
            hash_bar.finish()
702
            raiseCLIError(err, '"%s" not accessible' % container____path__)
703
        except IOError as err:
704
            progress_bar.finish()
705
            hash_bar.finish()
706
            raiseCLIError(err, 'Failed to read form file %s' % local_path, 2)
707
        print 'Upload completed'
708

    
709

    
710
@command(pithos_cmds)
711
class store_cat(_store_container_command):
712
    """Print a file to console"""
713

    
714
    def __init__(self, arguments={}):
715
        super(self.__class__, self).__init__(arguments)
716
        self.arguments['range'] =\
717
            RangeArgument('show range of data', '--range')
718
        self.arguments['if_match'] =\
719
            ValueArgument('show output if ETags match', '--if-match')
720
        self.arguments['if_none_match'] =\
721
            ValueArgument('show output if ETags match', '--if-none-match')
722
        self.arguments['if_modified_since'] =\
723
            DateArgument('show output modified since then',
724
            '--if-modified-since')
725
        self.arguments['if_unmodified_since'] =\
726
            DateArgument('show output unmodified since then',
727
            '--if-unmodified-since')
728
        self.arguments['object_version'] =\
729
            ValueArgument('get the specific version', '--object-version')
730

    
731
    def main(self, container___path):
732
        super(self.__class__,
733
            self).main(container___path, path_is_optional=False)
734
        try:
735
            self.client.download_object(self.path, stdout,
736
            range=self.get_argument('range'),
737
            version=self.get_argument('object_version'),
738
            if_match=self.get_argument('if_match'),
739
            if_none_match=self.get_argument('if_none_match'),
740
            if_modified_since=self.get_argument('if_modified_since'),
741
            if_unmodified_since=self.get_argument('if_unmodified_since'))
742
        except ClientError as err:
743
            raiseCLIError(err)
744

    
745

    
746
@command(pithos_cmds)
747
class store_download(_store_container_command):
748
    """Download a file"""
749

    
750
    def __init__(self, arguments={}):
751
        super(self.__class__, self).__init__(arguments)
752
        self.arguments['resume'] = FlagArgument(parsed_name='--resume',
753
            help='Resume a previous download instead of overwritting it')
754
        self.arguments['range'] = RangeArgument(\
755
            'show range of data', '--range')
756
        self.arguments['if_match'] = ValueArgument(\
757
            'show output if ETags match', '--if-match')
758
        self.arguments['if_none_match'] = ValueArgument(\
759
            'show output if ETags match', '--if-none-match')
760
        self.arguments['if_modified_since'] = DateArgument(\
761
            'show output modified since then', '--if-modified-since')
762
        self.arguments['if_unmodified_since'] = DateArgument(\
763
            'show output unmodified since then', '--if-unmodified-since')
764
        self.arguments['object_version'] = ValueArgument(\
765
            'get the specific version', '--object-version')
766
        self.arguments['poolsize'] = IntArgument(\
767
            'set pool size', '--with-pool-size')
768
        self.arguments['progress_bar'] = ProgressBarArgument(\
769
            'do not show progress bar', '--no-progress-bar', False)
770

    
771
    def main(self, container___path, local_path):
772
        super(self.__class__,
773
            self).main(container___path, path_is_optional=False)
774

    
775
        # setup output stream
776
        if local_path is None:
777
            out = stdout
778
        else:
779
            try:
780
                if self.get_argument('resume'):
781
                    out = open(local_path, 'rwb+')
782
                else:
783
                    out = open(local_path, 'wb+')
784
            except IOError as err:
785
                raiseCLIError(err, 'Cannot write to file %s' % local_path, 1)
786
        poolsize = self.get_argument('poolsize')
787
        if poolsize is not None:
788
            self.client.POOL_SIZE = int(poolsize)
789

    
790
        try:
791
            progress_bar = self.arguments['progress_bar']
792
            download_cb = progress_bar.get_generator('Downloading')
793
            self.client.download_object(self.path, out,
794
                download_cb=download_cb,
795
                range=self.get_argument('range'),
796
                version=self.get_argument('object_version'),
797
                if_match=self.get_argument('if_match'),
798
                resume=self.get_argument('resume'),
799
                if_none_match=self.get_argument('if_none_match'),
800
                if_modified_since=self.get_argument('if_modified_since'),
801
                if_unmodified_since=self.get_argument('if_unmodified_since'))
802
            progress_bar.finish()
803
        except ClientError as err:
804
            progress_bar.finish()
805
            raiseCLIError(err)
806
        except KeyboardInterrupt:
807
            from threading import enumerate as activethreads
808
            stdout.write('\nFinishing active threads ')
809
            for thread in activethreads():
810
                stdout.flush()
811
                try:
812
                    thread.join()
813
                    stdout.write('.')
814
                except RuntimeError:
815
                    continue
816
            progress_bar.finish()
817
            print('\ndownload canceled by user')
818
            if local_path is not None:
819
                print('to resume, re-run with --resume')
820
        except Exception as e:
821
            progress_bar.finish()
822
            raiseCLIError(e)
823
        print
824

    
825

    
826
@command(pithos_cmds)
827
class store_hashmap(_store_container_command):
828
    """Get the hashmap of an object"""
829

    
830
    def __init__(self, arguments={}):
831
        super(self.__class__, self).__init__(arguments)
832
        self.arguments['if_match'] =\
833
            ValueArgument('show output if ETags match', '--if-match')
834
        self.arguments['if_none_match'] =\
835
            ValueArgument('show output if ETags match', '--if-none-match')
836
        self.arguments['if_modified_since'] =\
837
            DateArgument('show output modified since then',
838
            '--if-modified-since')
839
        self.arguments['if_unmodified_since'] =\
840
            DateArgument('show output unmodified since then',
841
            '--if-unmodified-since')
842
        self.arguments['object_version'] =\
843
            ValueArgument('get the specific version', '--object-version')
844

    
845
    def main(self, container___path):
846
        super(self.__class__,
847
            self).main(container___path, path_is_optional=False)
848
        try:
849
            data = self.client.get_object_hashmap(self.path,
850
                version=self.get_argument('object_version'),
851
                if_match=self.get_argument('if_match'),
852
                if_none_match=self.get_argument('if_none_match'),
853
                if_modified_since=self.get_argument('if_modified_since'),
854
                if_unmodified_since=self.get_argument('if_unmodified_since'))
855
        except ClientError as err:
856
            raiseCLIError(err)
857
        print_dict(data)
858

    
859

    
860
@command(pithos_cmds)
861
class store_delete(_store_container_command):
862
    """Delete a container [or an object]"""
863

    
864
    def __init__(self, arguments={}):
865
        super(self.__class__, self).__init__(arguments)
866
        self.arguments['until'] = DateArgument(\
867
            'remove history until that date', '--until')
868
        self.arguments['recursive'] = FlagArgument(\
869
            'empty dir or container and delete (if dir)',
870
            ('-r', '--recursive'))
871
        self.arguments['delimiter'] = DelimiterArgument(self,
872
            parsed_name='--delimiter',
873
            help='delete objects prefixed with <object><delimiter>')
874

    
875
    def main(self, container____path__):
876
        super(self.__class__, self).main(container____path__)
877
        try:
878
            if self.path is None:
879
                self.client.del_container(until=self.get_argument('until'),
880
                    delimiter=self.get_argument('delimiter'))
881
            else:
882
                # self.client.delete_object(self.path)
883
                self.client.del_object(self.path,
884
                    until=self.get_argument('until'),
885
                    delimiter=self.get_argument('delimiter'))
886
        except ClientError as err:
887
            raiseCLIError(err)
888

    
889

    
890
@command(pithos_cmds)
891
class store_purge(_store_container_command):
892
    """Purge a container"""
893

    
894
    def main(self, container):
895
        super(self.__class__, self).main(container)
896
        try:
897
            self.client.purge_container()
898
        except ClientError as err:
899
            raiseCLIError(err)
900

    
901

    
902
@command(pithos_cmds)
903
class store_publish(_store_container_command):
904
    """Publish an object"""
905

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

    
915

    
916
@command(pithos_cmds)
917
class store_unpublish(_store_container_command):
918
    """Unpublish an object"""
919

    
920
    def main(self, container___path):
921
        super(self.__class__,
922
            self).main(container___path, path_is_optional=False)
923
        try:
924
            self.client.unpublish_object(self.path)
925
        except ClientError as err:
926
            raiseCLIError(err)
927

    
928

    
929
@command(pithos_cmds)
930
class store_permissions(_store_container_command):
931
    """Get object read / write permissions """
932

    
933
    def main(self, container___path):
934
        super(self.__class__,
935
            self).main(container___path, path_is_optional=False)
936
        try:
937
            reply = self.client.get_object_sharing(self.path)
938
            print_dict(reply)
939
        except ClientError as err:
940
            raiseCLIError(err)
941

    
942

    
943
@command(pithos_cmds)
944
class store_setpermissions(_store_container_command):
945
    """Set sharing permissions """
946

    
947
    def format_permition_dict(self, permissions):
948
        read = False
949
        write = False
950
        for perms in permissions:
951
            splstr = perms.split('=')
952
            if 'read' == splstr[0]:
953
                read = [user_or_group.strip() \
954
                for user_or_group in splstr[1].split(',')]
955
            elif 'write' == splstr[0]:
956
                write = [user_or_group.strip() \
957
                for user_or_group in splstr[1].split(',')]
958
            else:
959
                read = False
960
                write = False
961
        if not read and not write:
962
            raiseCLIError(None,
963
            'Usage:\tread=<groups,users> write=<groups,users>')
964
        return (read, write)
965

    
966
    def main(self, container___path, *permissions):
967
        super(self.__class__,
968
            self).main(container___path, path_is_optional=False)
969
        (read, write) = self.format_permition_dict(permissions)
970
        try:
971
            self.client.set_object_sharing(self.path,
972
                read_permition=read, write_permition=write)
973
        except ClientError as err:
974
            raiseCLIError(err)
975

    
976

    
977
@command(pithos_cmds)
978
class store_delpermissions(_store_container_command):
979
    """Delete all sharing permissions"""
980

    
981
    def main(self, container___path):
982
        super(self.__class__,
983
            self).main(container___path, path_is_optional=False)
984
        try:
985
            self.client.del_object_sharing(self.path)
986
        except ClientError as err:
987
            raiseCLIError(err)
988

    
989

    
990
@command(pithos_cmds)
991
class store_info(_store_container_command):
992
    """Get information for account [, container [or object]]"""
993

    
994
    def __init__(self, arguments={}):
995
        super(self.__class__, self).__init__(arguments)
996
        self.arguments['object_version'] =\
997
            ValueArgument(parsed_name='--object-version',
998
            help='show specific version \ (applies only for objects)')
999

    
1000
    def main(self, container____path__=None):
1001
        super(self.__class__, self).main(container____path__)
1002
        try:
1003
            if self.container is None:
1004
                reply = self.client.get_account_info()
1005
            elif self.path is None:
1006
                reply = self.client.get_container_info(self.container)
1007
            else:
1008
                reply = self.client.get_object_info(self.path,
1009
                    version=self.get_argument('object_version'))
1010
        except ClientError as err:
1011
            raiseCLIError(err)
1012
        print_dict(reply)
1013

    
1014

    
1015
@command(pithos_cmds)
1016
class store_meta(_store_container_command):
1017
    """Get custom meta-content for account [, container [or object]]"""
1018

    
1019
    def __init__(self, arguments={}):
1020
        super(self.__class__, self).__init__(arguments)
1021
        self.arguments['detail'] =\
1022
            FlagArgument('show detailed output', '-l')
1023
        self.arguments['until'] =\
1024
            DateArgument('show metadata until then', '--until')
1025
        self.arguments['object_version'] =\
1026
            ValueArgument(parsed_name='--object-version',
1027
            help='show specific version \ (applies only for objects)')
1028

    
1029
    def main(self, container____path__=None):
1030
        super(self.__class__, self).main(container____path__)
1031

    
1032
        detail = self.get_argument('detail')
1033
        try:
1034
            until = self.get_argument('until')
1035
            if self.container is None:
1036
                print(bold(self.client.account))
1037
                if detail:
1038
                    reply = self.client.get_account_info(until=until)
1039
                else:
1040
                    reply = self.client.get_account_meta(until=until)
1041
                    reply = pretty_keys(reply, '-')
1042
            elif self.path is None:
1043
                print(bold('%s: %s' % (self.client.account, self.container)))
1044
                if detail:
1045
                    reply = self.client.get_container_info(until=until)
1046
                else:
1047
                    cmeta = self.client.get_container_meta(until=until)
1048
                    ometa = self.client.get_container_object_meta(until=until)
1049
                    reply = {'container-meta': pretty_keys(cmeta, '-'),
1050
                        'object-meta': pretty_keys(ometa, '-')}
1051
            else:
1052
                print(bold('%s: %s:%s'\
1053
                    % (self.client.account, self.container, self.path)))
1054
                version = self.get_argument('object_version')
1055
                if detail:
1056
                    reply = self.client.get_object_info(self.path,
1057
                        version=version)
1058
                else:
1059
                    reply = self.client.get_object_meta(self.path,
1060
                        version=version)
1061
                    reply = pretty_keys(pretty_keys(reply, '-'))
1062
        except ClientError as err:
1063
            raiseCLIError(err)
1064
        print_dict(reply)
1065

    
1066

    
1067
@command(pithos_cmds)
1068
class store_setmeta(_store_container_command):
1069
    """Set a new metadatum for account [, container [or object]]"""
1070

    
1071
    def main(self, metakey___metaval, container____path__=None):
1072
        super(self.__class__, self).main(container____path__)
1073
        try:
1074
            metakey, metavalue = metakey___metaval.split(':')
1075
        except ValueError as err:
1076
            raiseCLIError(err, 'Usage:  metakey:metavalue', importance=1)
1077
        try:
1078
            if self.container is None:
1079
                self.client.set_account_meta({metakey: metavalue})
1080
            elif self.path is None:
1081
                self.client.set_container_meta({metakey: metavalue})
1082
            else:
1083
                self.client.set_object_meta(self.path, {metakey: metavalue})
1084
        except ClientError as err:
1085
            raiseCLIError(err)
1086

    
1087

    
1088
@command(pithos_cmds)
1089
class store_delmeta(_store_container_command):
1090
    """Delete an existing metadatum of account [, container [or object]]"""
1091

    
1092
    def main(self, metakey, container____path__=None):
1093
        super(self.__class__, self).main(container____path__)
1094
        try:
1095
            if self.container is None:
1096
                self.client.del_account_meta(metakey)
1097
            elif self.path is None:
1098
                self.client.del_container_meta(metakey)
1099
            else:
1100
                self.client.del_object_meta(self.path, metakey)
1101
        except ClientError as err:
1102
            raiseCLIError(err)
1103

    
1104

    
1105
@command(pithos_cmds)
1106
class store_quota(_store_account_command):
1107
    """Get  quota for account [or container]"""
1108

    
1109
    def main(self, container=None):
1110
        super(self.__class__, self).main()
1111
        try:
1112
            if container is None:
1113
                reply = self.client.get_account_quota()
1114
            else:
1115
                reply = self.client.get_container_quota(container)
1116
        except ClientError as err:
1117
            raiseCLIError(err)
1118
        print_dict(reply)
1119

    
1120

    
1121
@command(pithos_cmds)
1122
class store_setquota(_store_account_command):
1123
    """Set new quota (in KB) for account [or container]"""
1124

    
1125
    def main(self, quota, container=None):
1126
        super(self.__class__, self).main()
1127
        try:
1128
            if container is None:
1129
                self.client.set_account_quota(quota)
1130
            else:
1131
                self.client.container = container
1132
                self.client.set_container_quota(quota)
1133
        except ClientError as err:
1134
            raiseCLIError(err)
1135

    
1136

    
1137
@command(pithos_cmds)
1138
class store_versioning(_store_account_command):
1139
    """Get  versioning for account [or container ]"""
1140

    
1141
    def main(self, container=None):
1142
        super(self.__class__, self).main()
1143
        try:
1144
            if container is None:
1145
                reply = self.client.get_account_versioning()
1146
            else:
1147
                reply = self.client.get_container_versioning(container)
1148
        except ClientError as err:
1149
            raiseCLIError(err)
1150
        print_dict(reply)
1151

    
1152

    
1153
@command(pithos_cmds)
1154
class store_setversioning(_store_account_command):
1155
    """Set new versioning (auto, none) for account [or container]"""
1156

    
1157
    def main(self, versioning, container=None):
1158
        super(self.__class__, self).main()
1159
        try:
1160
            if container is None:
1161
                self.client.set_account_versioning(versioning)
1162
            else:
1163
                self.client.container = container
1164
                self.client.set_container_versioning(versioning)
1165
        except ClientError as err:
1166
            raiseCLIError(err)
1167

    
1168

    
1169
@command(pithos_cmds)
1170
class store_group(_store_account_command):
1171
    """Get user groups details for account"""
1172

    
1173
    def main(self):
1174
        super(self.__class__, self).main()
1175
        try:
1176
            reply = self.client.get_account_group()
1177
        except ClientError as err:
1178
            raiseCLIError(err)
1179
        print_dict(reply)
1180

    
1181

    
1182
@command(pithos_cmds)
1183
class store_setgroup(_store_account_command):
1184
    """Create/update a new user group on account"""
1185

    
1186
    def main(self, groupname, *users):
1187
        super(self.__class__, self).main()
1188
        try:
1189
            self.client.set_account_group(groupname, users)
1190
        except ClientError as err:
1191
            raiseCLIError(err)
1192

    
1193

    
1194
@command(pithos_cmds)
1195
class store_delgroup(_store_account_command):
1196
    """Delete a user group on an account"""
1197

    
1198
    def main(self, groupname):
1199
        super(self.__class__, self).main()
1200
        try:
1201
            self.client.del_account_group(groupname)
1202
        except ClientError as err:
1203
            raiseCLIError(err)
1204

    
1205

    
1206
@command(pithos_cmds)
1207
class store_sharers(_store_account_command):
1208
    """List the accounts that share objects with default account"""
1209

    
1210
    def __init__(self, arguments={}):
1211
        super(self.__class__, self).__init__(arguments)
1212
        self.arguments['detail'] =\
1213
            FlagArgument('show detailed output', '-l')
1214
        self.arguments['limit'] =\
1215
            IntArgument('show limited output', '--n', default=1000)
1216
        self.arguments['marker'] =\
1217
            ValueArgument('show output greater then marker', '--marker')
1218

    
1219
    def main(self):
1220
        super(self.__class__, self).main()
1221
        try:
1222
            marker = self.get_argument('marker')
1223
            accounts = self.client.get_sharing_accounts(marker=marker)
1224
        except ClientError as err:
1225
            raiseCLIError(err)
1226

    
1227
        for acc in accounts:
1228
            stdout.write(bold(acc['name']) + ' ')
1229
            if self.get_argument('detail'):
1230
                print_dict(acc, exclude='name', ident=4)
1231
        if not self.get_argument('detail'):
1232
            print
1233

    
1234

    
1235
@command(pithos_cmds)
1236
class store_versions(_store_container_command):
1237
    """Get the version list of an object"""
1238

    
1239
    def main(self, container___path):
1240
        super(store_versions, self).main(container___path)
1241
        try:
1242
            versions = self.client.get_object_versionlist(self.path)
1243
        except ClientError as err:
1244
            raiseCLIError(err)
1245

    
1246
        print('%s:%s versions' % (self.container, self.path))
1247
        for vitem in versions:
1248
            t = localtime(float(vitem[1]))
1249
            vid = bold(unicode(vitem[0]))
1250
            print('\t%s \t(%s)' % (vid, strftime('%d-%m-%Y %H:%M:%S', t)))