Statistics
| Branch: | Tag: | Revision:

root / pithos / tools / pithos-sh @ 93abb8b2

History | View | Annotate | Download (31.3 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 add_options(self, parser):
230
        parser.add_option('--versioning', action='store', dest='versioning',
231
                          default=None, help='set container versioning (auto/none)')
232
        parser.add_option('--quota', action='store', dest='quota',
233
                          default=None, help='set default container quota')
234
    
235
    def execute(self, container, *args):
236
        meta = {}
237
        for arg in args:
238
            key, sep, val = arg.partition('=')
239
            meta[key] = val
240
        policy = {}
241
        if getattr(self, 'versioning'):
242
            policy['versioning'] = self.versioning
243
        if getattr(self, 'quota'):
244
            policy['quota'] = self.quota
245
        ret = self.client.create_container(container, meta=meta, policies=policy)
246
        if not ret:
247
            print 'Container already exists'
248

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

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

    
349
@cli_command('mkdir')
350
class PutMarker(Command):
351
    syntax = '<container>/<directory marker>'
352
    description = 'create a directory marker'
353
    
354
    def execute(self, path):
355
        container, sep, object = path.partition('/')
356
        self.client.create_directory_marker(container, object)
357

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

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

    
473
@cli_command('set')
474
class SetMeta(Command):
475
    syntax = '[<container>[/<object>]] key=val [key=val] [...]'
476
    description = 'set account/container/object metadata'
477
    
478
    def execute(self, path, *args):
479
        #in case of account fix the args
480
        if path.find('=') != -1:
481
            args = list(args)
482
            args.append(path)
483
            args = tuple(args)
484
            path = ''
485
        meta = {}
486
        for arg in args:
487
            key, sep, val = arg.partition('=')
488
            meta[key.strip()] = val.strip()
489
        container, sep, object = path.partition('/')
490
        if object:
491
            self.client.update_object_metadata(container, object, **meta)
492
        elif container:
493
            self.client.update_container_metadata(container, **meta)
494
        else:
495
            self.client.update_account_metadata(**meta)
496

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

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

    
600
@cli_command('unset')
601
class UnsetObject(Command):
602
    syntax = '<container>/[<object>] key [key] [...]'
603
    description = 'delete metadata info'
604
    
605
    def execute(self, path, *args):
606
        #in case of account fix the args
607
        if len(args) == 0:
608
            args = list(args)
609
            args.append(path)
610
            args = tuple(args)
611
            path = ''
612
        meta = []
613
        for key in args:
614
            meta.append(key)
615
        container, sep, object = path.partition('/')
616
        if object:
617
            self.client.delete_object_metadata(container, object, meta)
618
        elif container:
619
            self.client.delete_container_metadata(container, meta)
620
        else:
621
            self.client.delete_account_metadata(meta)
622

    
623
@cli_command('group')
624
class CreateGroup(Command):
625
    syntax = 'key=val [key=val] [...]'
626
    description = 'create account groups'
627
    
628
    def execute(self, *args):
629
        groups = {}
630
        for arg in args:
631
            key, sep, val = arg.partition('=')
632
            groups[key] = val
633
        self.client.set_account_groups(**groups)
634

    
635
@cli_command('ungroup')
636
class DeleteGroup(Command):
637
    syntax = 'key [key] [...]'
638
    description = 'delete account groups'
639
    
640
    def execute(self, *args):
641
        groups = []
642
        for arg in args:
643
            groups.append(arg)
644
        self.client.unset_account_groups(groups)
645

    
646
@cli_command('policy')
647
class SetPolicy(Command):
648
    syntax = 'container key=val [key=val] [...]'
649
    description = 'set container policies'
650
    
651
    def execute(self, path, *args):
652
        if path.find('=') != -1:
653
            raise Fault('Missing container argument')
654
        
655
        container, sep, object = path.partition('/')
656
        
657
        if object:
658
            raise Fault('Only containers have policies')
659
        
660
        policies = {}
661
        for arg in args:
662
            key, sep, val = arg.partition('=')
663
            policies[key] = val
664
        
665
        self.client.set_container_policies(container, **policies)
666

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

    
677
@cli_command('unpublish')
678
class UnpublishObject(Command):
679
    syntax = '<container>/<object>'
680
    description = 'unpublish an object'
681
    
682
    def execute(self, src):
683
        src_container, sep, src_object = src.partition('/')
684
        
685
        self.client.unpublish_object(src_container, src_object)
686

    
687
@cli_command('sharing')
688
class SharingObject(Command):
689
    syntax = 'list users sharing objects with the user'
690
    description = 'list user accounts sharing objects with the user'
691
    
692
    def add_options(self, parser):
693
        parser.add_option('-l', action='store_true', dest='detail',
694
                          default=False, help='show detailed output')
695
        parser.add_option('-n', action='store', type='int', dest='limit',
696
                          default=10000, help='show limited output')
697
        parser.add_option('--marker', action='store', type='str',
698
                          dest='marker', default=None,
699
                          help='show output greater then marker')
700
        
701
    
702
    def execute(self):
703
        attrs = ['limit', 'marker']
704
        args = self._build_args(attrs)
705
        args['format'] = 'json' if self.detail else 'text'
706
        
707
        print_list(self.client.list_shared_by_others(**args))
708

    
709
@cli_command('send')
710
class Send(Command):
711
    syntax = '<file> <container>[/<prefix>]'
712
    description = 'upload file to container (using prefix)'
713
    
714
    def execute(self, file, path):
715
        container, sep, prefix = path.partition('/')
716
        upload(self.client, file, container, prefix)
717

    
718
@cli_command('receive')
719
class Receive(Command):
720
    syntax = '<container>/<object> <file>'
721
    description = 'download object to file'
722
    
723
    def execute(self, path, file):
724
        container, sep, object = path.partition('/')
725
        download(self.client, container, object, file)
726

    
727
def print_usage():
728
    cmd = Command('', [])
729
    parser = cmd.parser
730
    parser.usage = '%prog <command> [options]'
731
    parser.print_help()
732
    
733
    commands = []
734
    for cls in set(_cli_commands.values()):
735
        name = ', '.join(cls.commands)
736
        description = getattr(cls, 'description', '')
737
        commands.append('  %s %s' % (name.ljust(12), description))
738
    print '\nCommands:\n' + '\n'.join(sorted(commands))
739

    
740
def print_dict(d, header='name', f=stdout, detail=True):
741
    header = header if header in d else 'subdir'
742
    if header and header in d:
743
        f.write('%s\n' %d.pop(header).encode('utf8'))
744
    if detail:
745
        patterns = ['^x_(account|container|object)_meta_(\w+)$']
746
        patterns.append(patterns[0].replace('_', '-'))
747
        for key, val in sorted(d.items()):
748
            f.write('%s: %s\n' % (key.rjust(30), val))
749

    
750
def print_list(l, verbose=False, f=stdout, detail=True):
751
    for elem in l:
752
        #if it's empty string continue
753
        if not elem:
754
            continue
755
        if type(elem) == types.DictionaryType:
756
            print_dict(elem, f=f, detail=detail)
757
        elif type(elem) == types.StringType:
758
            if not verbose:
759
                elem = elem.split('Traceback')[0]
760
            f.write('%s\n' % elem)
761
        else:
762
            f.write('%s\n' % elem)
763

    
764
def print_versions(data, f=stdout):
765
    if 'versions' not in data:
766
        f.write('%s\n' %data)
767
        return
768
    f.write('versions:\n')
769
    for id, t in data['versions']:
770
        f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(float(t))))
771

    
772
def main():
773
    try:
774
        name = argv[1]
775
        cls = class_for_cli_command(name)
776
    except (IndexError, KeyError):
777
        print_usage()
778
        exit(1)
779
    
780
    cmd = cls(name, argv[2:])
781
    
782
    try:
783
        cmd.execute(*cmd.args)
784
    except TypeError, e:
785
        cmd.parser.print_help()
786
        exit(1)
787
    except Fault, f:
788
        status = '%s ' % f.status if f.status else ''
789
        print '%s%s' % (status, f.data)
790

    
791
if __name__ == '__main__':
792
    main()