Statistics
| Branch: | Tag: | Revision:

root / tools / store @ 317c3efc

History | View | Annotate | Download (30.3 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright 2011 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
from lib.client import Pithos_Client, Fault
42
from lib.util import get_user, get_auth, get_server, get_api
43
from lib.transfer import upload, download
44

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

    
52
_cli_commands = {}
53

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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