Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-tools / pithos / tools / pithos-sh @ 8c306eab

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

    
42
from pithos.lib.client import Pithos_Client, Fault
43
from pithos.lib.util import get_user, get_auth, get_server, get_api
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('--host', dest='host', metavar='HOST',
72
                          default=get_server(), help='use server HOST')
73
        parser.add_option('--user', dest='user', metavar='USERNAME',
74
                          default=get_user(),
75
                          help='use account USERNAME')
76
        parser.add_option('--token', dest='token', metavar='AUTH',
77
                          default=get_auth(),
78
                          help='use account AUTH')
79
        parser.add_option('--api', dest='api', metavar='API',
80
                          default=get_api(), help='use api API')
81
        parser.add_option('-v', action='store_true', dest='verbose',
82
                          default=False, help='use verbose output')
83
        parser.add_option('-d', action='store_true', dest='debug',
84
                          default=False, help='use debug output')
85
        self.add_options(parser)
86
        options, args = parser.parse_args(argv)
87
        
88
        # Add options to self
89
        for opt in parser.option_list:
90
            key = opt.dest
91
            if key:
92
                val = getattr(options, key)
93
                setattr(self, key, val)
94
        
95
        self.client = Pithos_Client(self.host, self.token, self.user, self.api, self.verbose,
96
                             self.debug)
97
        
98
        self.parser = parser
99
        self.args = args
100
    
101
    def _build_args(self, attrs):
102
        args = {}
103
        for a in [a for a in attrs if getattr(self, a)]:
104
            args[a] = getattr(self, a)
105
        return args
106

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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