Statistics
| Branch: | Tag: | Revision:

root / pithos / tools / pithos-sh @ f9e723fd

History | View | Annotate | Download (30.8 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
        parser.add_option('--hashmap', action='store_true',
295
                          dest='hashmap', default=False,
296
                          help='get the object hashmap instead')
297
    
298
    def execute(self, path):
299
        attrs = ['if_match', 'if_none_match', 'if_modified_since',
300
                 'if_unmodified_since', 'hashmap']
301
        args = self._build_args(attrs)
302
        args['format'] = 'json' if self.detail else 'text'
303
        if self.range:
304
            args['range'] = 'bytes=%s' % self.range
305
        if getattr(self, 'if_range'):
306
            args['if-range'] = 'If-Range:%s' % getattr(self, 'if_range')
307
        
308
        container, sep, object = path.partition('/')
309
        data = None
310
        if self.versionlist:
311
            if 'detail' in args.keys():
312
                args.pop('detail')
313
            args.pop('format')
314
            self.detail = True
315
            data = self.client.retrieve_object_versionlist(container, object, **args)
316
        elif self.version:
317
            data = self.client.retrieve_object_version(container, object,
318
                                                       self.version, **args)
319
        elif self.hashmap:
320
            if 'detail' in args.keys():
321
                args.pop('detail')
322
            args.pop('format')
323
            self.detail = True
324
            data = self.client.retrieve_object_hashmap(container, object, **args)
325
        else:
326
            data = self.client.retrieve_object(container, object, **args)    
327
        
328
        f = open(self.file, 'w') if self.file else stdout
329
        if self.detail or type(data) == types.DictionaryType:
330
            if self.versionlist:
331
                print_versions(data, f=f)
332
            else:
333
                print_dict(data, f=f)
334
        else:
335
            f.write(data)
336
        f.close()
337

    
338
@cli_command('mkdir')
339
class PutMarker(Command):
340
    syntax = '<container>/<directory marker>'
341
    description = 'create a directory marker'
342
    
343
    def execute(self, path):
344
        container, sep, object = path.partition('/')
345
        self.client.create_directory_marker(container, object)
346

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

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

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

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

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

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

    
612
@cli_command('group')
613
class CreateGroup(Command):
614
    syntax = 'key=val [key=val] [...]'
615
    description = 'create account groups'
616
    
617
    def execute(self, *args):
618
        groups = {}
619
        for arg in args:
620
            key, sep, val = arg.partition('=')
621
            groups[key] = val
622
        self.client.set_account_groups(**groups)
623

    
624
@cli_command('ungroup')
625
class DeleteGroup(Command):
626
    syntax = 'key [key] [...]'
627
    description = 'delete account groups'
628
    
629
    def execute(self, *args):
630
        groups = []
631
        for arg in args:
632
            groups.append(arg)
633
        self.client.unset_account_groups(groups)
634

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

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

    
666
@cli_command('unpublish')
667
class UnpublishObject(Command):
668
    syntax = '<container>/<object>'
669
    description = 'unpublish an object'
670
    
671
    def execute(self, src):
672
        src_container, sep, src_object = src.partition('/')
673
        
674
        self.client.unpublish_object(src_container, src_object)
675

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

    
698
@cli_command('send')
699
class Send(Command):
700
    syntax = '<file> <container>[/<prefix>]'
701
    description = 'upload file to container (using prefix)'
702
    
703
    def execute(self, file, path):
704
        container, sep, prefix = path.partition('/')
705
        upload(self.client, file, container, prefix)
706

    
707
@cli_command('receive')
708
class Receive(Command):
709
    syntax = '<container>/<object> <file>'
710
    description = 'download object to file'
711
    
712
    def execute(self, path, file):
713
        container, sep, object = path.partition('/')
714
        download(self.client, container, object, file)
715

    
716
def print_usage():
717
    cmd = Command('', [])
718
    parser = cmd.parser
719
    parser.usage = '%prog <command> [options]'
720
    parser.print_help()
721
    
722
    commands = []
723
    for cls in set(_cli_commands.values()):
724
        name = ', '.join(cls.commands)
725
        description = getattr(cls, 'description', '')
726
        commands.append('  %s %s' % (name.ljust(12), description))
727
    print '\nCommands:\n' + '\n'.join(sorted(commands))
728

    
729
def print_dict(d, header='name', f=stdout, detail=True):
730
    header = header if header in d else 'subdir'
731
    if header and header in d:
732
        f.write('%s\n' %d.pop(header).encode('utf8'))
733
    if detail:
734
        patterns = ['^x_(account|container|object)_meta_(\w+)$']
735
        patterns.append(patterns[0].replace('_', '-'))
736
        for key, val in sorted(d.items()):
737
            f.write('%s: %s\n' % (key.rjust(30), val))
738

    
739
def print_list(l, verbose=False, f=stdout, detail=True):
740
    for elem in l:
741
        #if it's empty string continue
742
        if not elem:
743
            continue
744
        if type(elem) == types.DictionaryType:
745
            print_dict(elem, f=f, detail=detail)
746
        elif type(elem) == types.StringType:
747
            if not verbose:
748
                elem = elem.split('Traceback')[0]
749
            f.write('%s\n' % elem)
750
        else:
751
            f.write('%s\n' % elem)
752

    
753
def print_versions(data, f=stdout):
754
    if 'versions' not in data:
755
        f.write('%s\n' %data)
756
        return
757
    f.write('versions:\n')
758
    for id, t in data['versions']:
759
        f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(float(t))))
760

    
761
def main():
762
    try:
763
        name = argv[1]
764
        cls = class_for_cli_command(name)
765
    except (IndexError, KeyError):
766
        print_usage()
767
        exit(1)
768
    
769
    cmd = cls(name, argv[2:])
770
    
771
    try:
772
        cmd.execute(*cmd.args)
773
    except TypeError, e:
774
        cmd.parser.print_help()
775
        exit(1)
776
    except Fault, f:
777
        status = '%s ' % f.status if f.status else ''
778
        print '%s%s' % (status, f.data)
779

    
780
if __name__ == '__main__':
781
    main()