Statistics
| Branch: | Tag: | Revision:

root / pithos / tools / pithos-sh @ 96738a78

History | View | Annotate | Download (30.2 kB)

1
#!/usr/bin/env python
2

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

    
36
from getpass import getuser
37
from optparse import OptionParser
38
from os import environ
39
from sys import argv, exit, stdin, stdout
40
from datetime import datetime
41

    
42
from pithos.lib.client import Pithos_Client, Fault
43
from pithos.lib.util import get_user, get_auth, get_url
44
from pithos.lib.transfer import upload, download
45

    
46
import json
47
import logging
48
import types
49
import re
50
import time as _time
51
import os
52

    
53
_cli_commands = {}
54

    
55
def cli_command(*args):
56
    def decorator(cls):
57
        cls.commands = args
58
        for name in args:
59
            _cli_commands[name] = cls
60
        return cls
61
    return decorator
62

    
63
def class_for_cli_command(name):
64
    return _cli_commands[name]
65

    
66
class Command(object):
67
    syntax = ''
68
    
69
    def __init__(self, name, argv):
70
        parser = OptionParser('%%prog %s [options] %s' % (name, self.syntax))
71
        parser.add_option('--url', dest='url', metavar='URL',
72
                          default=get_url(), help='server URL (currently: %s)' % get_url())
73
        parser.add_option('--user', dest='user', metavar='USER',
74
                          default=get_user(),
75
                          help='account USER (currently: %s)' % get_user())
76
        parser.add_option('--token', dest='token', metavar='TOKEN',
77
                          default=get_auth(),
78
                          help='account TOKEN (currently: %s)' % get_auth())
79
        parser.add_option('-v', action='store_true', dest='verbose',
80
                          default=False, help='verbose output')
81
        parser.add_option('-d', action='store_true', dest='debug',
82
                          default=False, help='debug output')
83
        self.add_options(parser)
84
        options, args = parser.parse_args(argv)
85
        
86
        # Add options to self
87
        for opt in parser.option_list:
88
            key = opt.dest
89
            if key:
90
                val = getattr(options, key)
91
                setattr(self, key, val)
92
        
93
        self.client = Pithos_Client(self.url, self.token, self.user, self.verbose,
94
                             self.debug)
95
        
96
        self.parser = parser
97
        self.args = args
98
    
99
    def _build_args(self, attrs):
100
        args = {}
101
        for a in [a for a in attrs if getattr(self, a)]:
102
            args[a] = getattr(self, a)
103
        return args
104

    
105
    def add_options(self, parser):
106
        pass
107
    
108
    def execute(self, *args):
109
        pass
110

    
111
@cli_command('list', 'ls')
112
class List(Command):
113
    syntax = '[<container>[/<object>]]'
114
    description = 'list containers or objects'
115
    
116
    def add_options(self, parser):
117
        parser.add_option('-l', action='store_true', dest='detail',
118
                          default=False, help='show detailed output')
119
        parser.add_option('-n', action='store', type='int', dest='limit',
120
                          default=10000, help='show limited output')
121
        parser.add_option('--marker', action='store', type='str',
122
                          dest='marker', default=None,
123
                          help='show output greater then marker')
124
        parser.add_option('--prefix', action='store', type='str',
125
                          dest='prefix', default=None,
126
                          help='show output starting with prefix')
127
        parser.add_option('--delimiter', action='store', type='str',
128
                          dest='delimiter', default=None,
129
                          help='show output up to the delimiter')
130
        parser.add_option('--path', action='store', type='str',
131
                          dest='path', default=None,
132
                          help='show output starting with prefix up to /')
133
        parser.add_option('--meta', action='store', type='str',
134
                          dest='meta', default=None,
135
                          help='show output having the specified meta keys')
136
        parser.add_option('--if-modified-since', action='store', type='str',
137
                          dest='if_modified_since', default=None,
138
                          help='show output if modified since then')
139
        parser.add_option('--if-unmodified-since', action='store', type='str',
140
                          dest='if_unmodified_since', default=None,
141
                          help='show output if not modified since then')
142
        parser.add_option('--until', action='store', dest='until',
143
                          default=None, help='show metadata until that date')
144
        parser.add_option('--format', action='store', dest='format',
145
                          default='%d/%m/%Y', help='format to parse until date')
146
    
147
    def execute(self, container=None):
148
        if container:
149
            self.list_objects(container)
150
        else:
151
            self.list_containers()
152
    
153
    def list_containers(self):
154
        attrs = ['limit', 'marker', 'if_modified_since',
155
                 'if_unmodified_since']
156
        args = self._build_args(attrs)
157
        args['format'] = 'json' if self.detail else 'text'
158
        
159
        if getattr(self, 'until'):
160
            t = _time.strptime(self.until, self.format)
161
            args['until'] = int(_time.mktime(t))
162
        
163
        l = self.client.list_containers(**args)
164
        print_list(l)
165
    
166
    def list_objects(self, container):
167
        #prepate params
168
        params = {}
169
        attrs = ['limit', 'marker', 'prefix', 'delimiter', 'path',
170
                 'meta', 'if_modified_since', 'if_unmodified_since']
171
        args = self._build_args(attrs)
172
        args['format'] = 'json' if self.detail else 'text'
173
        
174
        if self.until:
175
            t = _time.strptime(self.until, self.format)
176
            args['until'] = int(_time.mktime(t))
177
        
178
        container, sep, object = container.partition('/')
179
        if object:
180
            return
181
        
182
        detail = 'json'
183
        #if request with meta quering disable trash filtering
184
        show_trashed = True if self.meta else False
185
        l = self.client.list_objects(container, **args)
186
        print_list(l, detail=self.detail)
187

    
188
@cli_command('meta')
189
class Meta(Command):
190
    syntax = '[<container>[/<object>]]'
191
    description = 'get account/container/object metadata'
192
    
193
    def add_options(self, parser):
194
        parser.add_option('-r', action='store_true', dest='restricted',
195
                          default=False, help='show only user defined metadata')
196
        parser.add_option('--until', action='store', dest='until',
197
                          default=None, help='show metadata until that date')
198
        parser.add_option('--format', action='store', dest='format',
199
                          default='%d/%m/%Y', help='format to parse until date')
200
        parser.add_option('--version', action='store', dest='version',
201
                          default=None, help='show specific version \
202
                                  (applies only for objects)')
203
    
204
    def execute(self, path=''):
205
        container, sep, object = path.partition('/')
206
        args = {'restricted': self.restricted}
207
        if getattr(self, 'until'):
208
            t = _time.strptime(self.until, self.format)
209
            args['until'] = int(_time.mktime(t))
210
        
211
        if object:
212
            meta = self.client.retrieve_object_metadata(container, object,
213
                                                        self.restricted,
214
                                                        self.version)
215
        elif container:
216
            meta = self.client.retrieve_container_metadata(container, **args)
217
        else:
218
            meta = self.client.retrieve_account_metadata(**args)
219
        if meta == None:
220
            print 'Entity does not exist'
221
        else:
222
            print_dict(meta, header=None)
223

    
224
@cli_command('create')
225
class CreateContainer(Command):
226
    syntax = '<container> [key=val] [...]'
227
    description = 'create a container'
228
    
229
    def execute(self, container, *args):
230
        meta = {}
231
        for arg in args:
232
            key, sep, val = arg.partition('=')
233
            meta[key] = val
234
        ret = self.client.create_container(container, **meta)
235
        if not ret:
236
            print 'Container already exists'
237

    
238
@cli_command('delete', 'rm')
239
class Delete(Command):
240
    syntax = '<container>[/<object>]'
241
    description = 'delete a container or an object'
242
    
243
    def add_options(self, parser):
244
        parser.add_option('--until', action='store', dest='until',
245
                          default=None, help='remove history until that date')
246
        parser.add_option('--format', action='store', dest='format',
247
                          default='%d/%m/%Y', help='format to parse until date')
248
    
249
    def execute(self, path):
250
        container, sep, object = path.partition('/')
251
        until = None
252
        if getattr(self, 'until'):
253
            t = _time.strptime(self.until, self.format)
254
            until = int(_time.mktime(t))
255
        
256
        if object:
257
            self.client.delete_object(container, object, until)
258
        else:
259
            self.client.delete_container(container, until)
260

    
261
@cli_command('get')
262
class GetObject(Command):
263
    syntax = '<container>/<object>'
264
    description = 'get the data of an object'
265
    
266
    def add_options(self, parser):
267
        parser.add_option('-l', action='store_true', dest='detail',
268
                          default=False, help='show detailed output')
269
        parser.add_option('--range', action='store', dest='range',
270
                          default=None, help='show range of data')
271
        parser.add_option('--if-range', action='store', dest='if_range',
272
                          default=None, help='show range of data')
273
        parser.add_option('--if-match', action='store', dest='if_match',
274
                          default=None, help='show output if ETags match')
275
        parser.add_option('--if-none-match', action='store',
276
                          dest='if_none_match', default=None,
277
                          help='show output if ETags don\'t match')
278
        parser.add_option('--if-modified-since', action='store', type='str',
279
                          dest='if_modified_since', default=None,
280
                          help='show output if modified since then')
281
        parser.add_option('--if-unmodified-since', action='store', type='str',
282
                          dest='if_unmodified_since', default=None,
283
                          help='show output if not modified since then')
284
        parser.add_option('-o', action='store', type='str',
285
                          dest='file', default=None,
286
                          help='save output in file')
287
        parser.add_option('--version', action='store', type='str',
288
                          dest='version', default=None,
289
                          help='get the specific \
290
                               version')
291
        parser.add_option('--versionlist', action='store_true',
292
                          dest='versionlist', default=False,
293
                          help='get the full object version list')
294
    
295
    def execute(self, path):
296
        attrs = ['if_match', 'if_none_match', 'if_modified_since',
297
                 'if_unmodified_since']
298
        args = self._build_args(attrs)
299
        args['format'] = 'json' if self.detail else 'text'
300
        if self.range:
301
            args['range'] = 'bytes=%s' % self.range
302
        if getattr(self, 'if_range'):
303
            args['if-range'] = 'If-Range:%s' % getattr(self, 'if_range')
304
        
305
        container, sep, object = path.partition('/')
306
        data = None
307
        if self.versionlist:
308
            if 'detail' in args.keys():
309
                args.pop('detail')
310
            args.pop('format')
311
            self.detail = True
312
            data = self.client.retrieve_object_versionlist(container, object, **args)
313
        elif self.version:
314
            data = self.client.retrieve_object_version(container, object,
315
                                                       self.version, **args)
316
        else:
317
            data = self.client.retrieve_object(container, object, **args)    
318
        
319
        f = open(self.file, 'w') if self.file else stdout
320
        if self.detail:
321
            if self.versionlist:
322
                print_versions(data, f=f)
323
            else:
324
                print_dict(data, f=f)
325
        else:
326
            f.write(data)
327
        f.close()
328

    
329
@cli_command('mkdir')
330
class PutMarker(Command):
331
    syntax = '<container>/<directory marker>'
332
    description = 'create a directory marker'
333
    
334
    def execute(self, path):
335
        container, sep, object = path.partition('/')
336
        self.client.create_directory_marker(container, object)
337

    
338
@cli_command('put')
339
class PutObject(Command):
340
    syntax = '<container>/<object> [key=val] [...]'
341
    description = 'create/override object'
342
    
343
    def add_options(self, parser):
344
        parser.add_option('--use_hashes', action='store_true', dest='use_hashes',
345
                          default=False, help='provide hashmap instead of data')
346
        parser.add_option('--chunked', action='store_true', dest='chunked',
347
                          default=False, help='set chunked transfer mode')
348
        parser.add_option('--etag', action='store', dest='etag',
349
                          default=None, help='check written data')
350
        parser.add_option('--content-encoding', action='store',
351
                          dest='content_encoding', default=None,
352
                          help='provide the object MIME content type')
353
        parser.add_option('--content-disposition', action='store', type='str',
354
                          dest='content_disposition', default=None,
355
                          help='provide the presentation style of the object')
356
        #parser.add_option('-S', action='store',
357
        #                  dest='segment_size', default=False,
358
        #                  help='use for large file support')
359
        parser.add_option('--manifest', action='store',
360
                          dest='x_object_manifest', default=None,
361
                          help='upload a manifestation file')
362
        parser.add_option('--content-type', action='store',
363
                          dest='content_type', default=None,
364
                          help='create object with specific content type')
365
        parser.add_option('--sharing', action='store',
366
                          dest='x_object_sharing', default=None,
367
                          help='define sharing object policy')
368
        parser.add_option('-f', action='store',
369
                          dest='srcpath', default=None,
370
                          help='file descriptor to read from (pass - for standard input)')
371
        parser.add_option('--public', action='store_true',
372
                          dest='x_object_public', default=False,
373
                          help='make object publicly accessible')
374
    
375
    def execute(self, path, *args):
376
        if path.find('=') != -1:
377
            raise Fault('Missing path argument')
378
        
379
        #prepare user defined meta
380
        meta = {}
381
        for arg in args:
382
            key, sep, val = arg.partition('=')
383
            meta[key] = val
384
        
385
        attrs = ['etag', 'content_encoding', 'content_disposition',
386
                 'content_type', 'x_object_sharing', 'x_object_public']
387
        args = self._build_args(attrs)
388
        
389
        container, sep, object = path.partition('/')
390
        
391
        f = None
392
        if self.srcpath:
393
            f = open(self.srcpath) if self.srcpath != '-' else stdin
394
        
395
        if self.use_hashes and not f:
396
            raise Fault('Illegal option combination')
397
        
398
        if self.chunked:
399
            self.client.create_object_using_chunks(container, object, f,
400
                                                    meta=meta, **args)
401
        elif self.use_hashes:
402
            data = f.read()
403
            if data is object:
404
                hashmap = json.loads()
405
                self.client.create_object_by_hashmap(container, object, hashmap,
406
                                                 meta=meta, **args)
407
            else:
408
                print "Expected object"
409
        elif self.x_object_manifest:
410
            self.client.create_manifestation(container, object, self.x_object_manifest)
411
        elif not f:
412
            self.client.create_zero_length_object(container, object, meta=meta, **args)
413
        else:
414
            self.client.create_object(container, object, f, meta=meta, **args)
415
        if f:
416
            f.close()
417

    
418
@cli_command('copy', 'cp')
419
class CopyObject(Command):
420
    syntax = '<src container>/<src object> [<dst container>/]<dst object> [key=val] [...]'
421
    description = 'copy an object to a different location'
422
    
423
    def add_options(self, parser):
424
        parser.add_option('--version', action='store',
425
                          dest='version', default=False,
426
                          help='copy specific version')
427
        parser.add_option('--public', action='store_true',
428
                          dest='public', default=False,
429
                          help='make object publicly accessible')
430
        parser.add_option('--content-type', action='store',
431
                          dest='content_type', default=None,
432
                          help='change object\'s content type')
433
    
434
    def execute(self, src, dst, *args):
435
        src_container, sep, src_object = src.partition('/')
436
        dst_container, sep, dst_object = dst.partition('/')
437
        
438
        #prepare user defined meta
439
        meta = {}
440
        for arg in args:
441
            key, sep, val = arg.partition('=')
442
            meta[key] = val
443
        
444
        if not sep:
445
            dst_container = src_container
446
            dst_object = dst
447
        
448
        args = {'content_type':self.content_type} if self.content_type else {}
449
        self.client.copy_object(src_container, src_object, dst_container,
450
                                dst_object, meta, self.public, self.version,
451
                                **args)
452

    
453
@cli_command('set')
454
class SetMeta(Command):
455
    syntax = '[<container>[/<object>]] key=val [key=val] [...]'
456
    description = 'set account/container/object metadata'
457
    
458
    def execute(self, path, *args):
459
        #in case of account fix the args
460
        if path.find('=') != -1:
461
            args = list(args)
462
            args.append(path)
463
            args = tuple(args)
464
            path = ''
465
        meta = {}
466
        for arg in args:
467
            key, sep, val = arg.partition('=')
468
            meta[key.strip()] = val.strip()
469
        container, sep, object = path.partition('/')
470
        if object:
471
            self.client.update_object_metadata(container, object, **meta)
472
        elif container:
473
            self.client.update_container_metadata(container, **meta)
474
        else:
475
            self.client.update_account_metadata(**meta)
476

    
477
@cli_command('update')
478
class UpdateObject(Command):
479
    syntax = '<container>/<object> path [key=val] [...]'
480
    description = 'update object metadata/data (default mode: append)'
481
    
482
    def add_options(self, parser):
483
        parser.add_option('-a', action='store_true', dest='append',
484
                          default=True, help='append data')
485
        parser.add_option('--offset', action='store',
486
                          dest='offset',
487
                          default=None, help='starting offest to be updated')
488
        parser.add_option('--range', action='store', dest='content-range',
489
                          default=None, help='range of data to be updated')
490
        parser.add_option('--chunked', action='store_true', dest='chunked',
491
                          default=False, help='set chunked transfer mode')
492
        parser.add_option('--content-encoding', action='store',
493
                          dest='content_encoding', default=None,
494
                          help='provide the object MIME content type')
495
        parser.add_option('--content-disposition', action='store', type='str',
496
                          dest='content_disposition', default=None,
497
                          help='provide the presentation style of the object')
498
        parser.add_option('--manifest', action='store', type='str',
499
                          dest='x_object_manifest', default=None,
500
                          help='use for large file support')        
501
        parser.add_option('--sharing', action='store',
502
                          dest='x_object_sharing', default=None,
503
                          help='define sharing object policy')
504
        parser.add_option('--nosharing', action='store_true',
505
                          dest='no_sharing', default=None,
506
                          help='clear object sharing policy')
507
        parser.add_option('-f', action='store',
508
                          dest='srcpath', default=None,
509
                          help='file descriptor to read from: pass - for standard input')
510
        parser.add_option('--public', action='store_true',
511
                          dest='x_object_public', default=False,
512
                          help='make object publicly accessible')
513
        parser.add_option('--replace', action='store_true',
514
                          dest='replace', default=False,
515
                          help='override metadata')
516
    
517
    def execute(self, path, *args):
518
        if path.find('=') != -1:
519
            raise Fault('Missing path argument')
520
        
521
        #prepare user defined meta
522
        meta = {}
523
        for arg in args:
524
            key, sep, val = arg.partition('=')
525
            meta[key] = val
526
        
527
        
528
        attrs = ['content_encoding', 'content_disposition', 'x_object_sharing',
529
                 'x_object_public', 'replace']
530
        args = self._build_args(attrs)
531
        
532
        if self.no_sharing:
533
            args['x_object_sharing'] = ''
534
        
535
        container, sep, object = path.partition('/')
536
        
537
        f = None
538
        if self.srcpath:
539
            f = open(self.srcpath) if self.srcpath != '-' else stdin
540
        
541
        if self.chunked:
542
            self.client.update_object_using_chunks(container, object, f,
543
                                                    meta=meta, **args)
544
        else:
545
            self.client.update_object(container, object, f, meta=meta, **args)
546
        if f:
547
            f.close()
548

    
549
@cli_command('move', 'mv')
550
class MoveObject(Command):
551
    syntax = '<src container>/<src object> [<dst container>/]<dst object>'
552
    description = 'move an object to a different location'
553
    
554
    def add_options(self, parser):
555
        parser.add_option('--public', action='store_true',
556
                          dest='public', default=False,
557
                          help='make object publicly accessible')
558
        parser.add_option('--content-type', action='store',
559
                          dest='content_type', default=None,
560
                          help='change object\'s content type')
561
    
562
    def execute(self, src, dst, *args):
563
        src_container, sep, src_object = src.partition('/')
564
        dst_container, sep, dst_object = dst.partition('/')
565
        if not sep:
566
            dst_container = src_container
567
            dst_object = dst
568
        
569
        #prepare user defined meta
570
        meta = {}
571
        for arg in args:
572
            key, sep, val = arg.partition('=')
573
            meta[key] = val
574
        
575
        args = {'content_type':self.content_type} if self.content_type else {}
576
        self.client.move_object(src_container, src_object, dst_container,
577
                                dst_object, meta, self.public, **args)
578

    
579
@cli_command('unset')
580
class UnsetObject(Command):
581
    syntax = '<container>/[<object>] key [key] [...]'
582
    description = 'delete metadata info'
583
    
584
    def execute(self, path, *args):
585
        #in case of account fix the args
586
        if len(args) == 0:
587
            args = list(args)
588
            args.append(path)
589
            args = tuple(args)
590
            path = ''
591
        meta = []
592
        for key in args:
593
            meta.append(key)
594
        container, sep, object = path.partition('/')
595
        if object:
596
            self.client.delete_object_metadata(container, object, meta)
597
        elif container:
598
            self.client.delete_container_metadata(container, meta)
599
        else:
600
            self.client.delete_account_metadata(meta)
601

    
602
@cli_command('group')
603
class CreateGroup(Command):
604
    syntax = 'key=val [key=val] [...]'
605
    description = 'create account groups'
606
    
607
    def execute(self, *args):
608
        groups = {}
609
        for arg in args:
610
            key, sep, val = arg.partition('=')
611
            groups[key] = val
612
        self.client.set_account_groups(**groups)
613

    
614
@cli_command('ungroup')
615
class DeleteGroup(Command):
616
    syntax = 'key [key] [...]'
617
    description = 'delete account groups'
618
    
619
    def execute(self, *args):
620
        groups = []
621
        for arg in args:
622
            groups.append(arg)
623
        self.client.unset_account_groups(groups)
624

    
625
@cli_command('policy')
626
class SetPolicy(Command):
627
    syntax = 'container key=val [key=val] [...]'
628
    description = 'set container policies'
629
    
630
    def execute(self, path, *args):
631
        if path.find('=') != -1:
632
            raise Fault('Missing container argument')
633
        
634
        container, sep, object = path.partition('/')
635
        
636
        if object:
637
            raise Fault('Only containers have policies')
638
        
639
        policies = {}
640
        for arg in args:
641
            key, sep, val = arg.partition('=')
642
            policies[key] = val
643
        
644
        self.client.set_container_policies(container, **policies)
645

    
646
@cli_command('publish')
647
class PublishObject(Command):
648
    syntax = '<container>/<object>'
649
    description = 'publish an object'
650
    
651
    def execute(self, src):
652
        src_container, sep, src_object = src.partition('/')
653
        
654
        self.client.publish_object(src_container, src_object)
655

    
656
@cli_command('unpublish')
657
class UnpublishObject(Command):
658
    syntax = '<container>/<object>'
659
    description = 'unpublish an object'
660
    
661
    def execute(self, src):
662
        src_container, sep, src_object = src.partition('/')
663
        
664
        self.client.unpublish_object(src_container, src_object)
665

    
666
@cli_command('sharing')
667
class SharingObject(Command):
668
    syntax = 'list users sharing objects with the user'
669
    description = 'list user accounts sharing objects with the user'
670
    
671
    def add_options(self, parser):
672
        parser.add_option('-l', action='store_true', dest='detail',
673
                          default=False, help='show detailed output')
674
        parser.add_option('-n', action='store', type='int', dest='limit',
675
                          default=10000, help='show limited output')
676
        parser.add_option('--marker', action='store', type='str',
677
                          dest='marker', default=None,
678
                          help='show output greater then marker')
679
        
680
    
681
    def execute(self):
682
        attrs = ['limit', 'marker']
683
        args = self._build_args(attrs)
684
        args['format'] = 'json' if self.detail else 'text'
685
        
686
        print_list(self.client.list_shared_by_others(**args))
687

    
688
@cli_command('send')
689
class Send(Command):
690
    syntax = '<file> <container>[/<prefix>]'
691
    description = 'upload file to container (using prefix)'
692
    
693
    def execute(self, file, path):
694
        container, sep, prefix = path.partition('/')
695
        upload(self.client, file, container, prefix)
696

    
697
@cli_command('receive')
698
class Receive(Command):
699
    syntax = '<container>/<object> <file>'
700
    description = 'download object to file'
701
    
702
    def execute(self, path, file):
703
        container, sep, object = path.partition('/')
704
        download(self.client, container, object, file)
705

    
706
def print_usage():
707
    cmd = Command('', [])
708
    parser = cmd.parser
709
    parser.usage = '%prog <command> [options]'
710
    parser.print_help()
711
    
712
    commands = []
713
    for cls in set(_cli_commands.values()):
714
        name = ', '.join(cls.commands)
715
        description = getattr(cls, 'description', '')
716
        commands.append('  %s %s' % (name.ljust(12), description))
717
    print '\nCommands:\n' + '\n'.join(sorted(commands))
718

    
719
def print_dict(d, header='name', f=stdout, detail=True):
720
    header = header if header in d else 'subdir'
721
    if header and header in d:
722
        f.write('%s\n' %d.pop(header).encode('utf8'))
723
    if detail:
724
        patterns = ['^x_(account|container|object)_meta_(\w+)$']
725
        patterns.append(patterns[0].replace('_', '-'))
726
        for key, val in sorted(d.items()):
727
            f.write('%s: %s\n' % (key.rjust(30), val))
728

    
729
def print_list(l, verbose=False, f=stdout, detail=True):
730
    for elem in l:
731
        #if it's empty string continue
732
        if not elem:
733
            continue
734
        if type(elem) == types.DictionaryType:
735
            print_dict(elem, f=f, detail=detail)
736
        elif type(elem) == types.StringType:
737
            if not verbose:
738
                elem = elem.split('Traceback')[0]
739
            f.write('%s\n' % elem)
740
        else:
741
            f.write('%s\n' % elem)
742

    
743
def print_versions(data, f=stdout):
744
    if 'versions' not in data:
745
        f.write('%s\n' %data)
746
        return
747
    f.write('versions:\n')
748
    for id, t in data['versions']:
749
        f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(float(t))))
750

    
751
def main():
752
    try:
753
        name = argv[1]
754
        cls = class_for_cli_command(name)
755
    except (IndexError, KeyError):
756
        print_usage()
757
        exit(1)
758
    
759
    cmd = cls(name, argv[2:])
760
    
761
    try:
762
        cmd.execute(*cmd.args)
763
    except TypeError, e:
764
        cmd.parser.print_help()
765
        exit(1)
766
    except Fault, f:
767
        status = '%s ' % f.status if f.status else ''
768
        print '%s%s' % (status, f.data)
769

    
770
if __name__ == '__main__':
771
    main()