Statistics
| Branch: | Tag: | Revision:

root / tools / store @ cd6ada1d

History | View | Annotate | Download (29.2 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 pithos.lib.client import Pithos_Client, Fault
41
from datetime import datetime
42

    
43
import json
44
import logging
45
import types
46
import re
47
import time as _time
48
import os
49

    
50
#DEFAULT_HOST = 'pithos.dev.grnet.gr'
51
DEFAULT_HOST = '127.0.0.1:8000'
52
DEFAULT_API = 'v1'
53

    
54
_cli_commands = {}
55

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

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

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

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

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

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

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

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

    
264
@cli_command('get')
265
class GetObject(Command):
266
    syntax = '<container>/<object>'
267
    description = 'get the data of an object'
268
    
269
    def add_options(self, parser):
270
        parser.add_option('-l', action='store_true', dest='detail',
271
                          default=False, help='show detailed output')
272
        parser.add_option('--range', action='store', dest='range',
273
                          default=None, help='show range of data')
274
        parser.add_option('--if-range', action='store', dest='if_range',
275
                          default=None, help='show range of data')
276
        parser.add_option('--if-match', action='store', dest='if_match',
277
                          default=None, help='show output if ETags match')
278
        parser.add_option('--if-none-match', action='store',
279
                          dest='if_none_match', default=None,
280
                          help='show output if ETags don\'t match')
281
        parser.add_option('--if-modified-since', action='store', type='str',
282
                          dest='if_modified_since', default=None,
283
                          help='show output if modified since then')
284
        parser.add_option('--if-unmodified-since', action='store', type='str',
285
                          dest='if_unmodified_since', default=None,
286
                          help='show output if not modified since then')
287
        parser.add_option('-o', action='store', type='str',
288
                          dest='file', default=None,
289
                          help='save output in file')
290
        parser.add_option('--version', action='store', type='str',
291
                          dest='version', default=None,
292
                          help='get the specific \
293
                               version')
294
        parser.add_option('--versionlist', action='store_true',
295
                          dest='versionlist', default=False,
296
                          help='get the full object version list')
297
    
298
    def execute(self, path):
299
        attrs = ['if_match', 'if_none_match', 'if_modified_since',
300
                 'if_unmodified_since']
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
            data = self.client.retrieve_object_versionlist(container, object, **args)
314
        elif self.version:
315
            data = self.client.retrieve_object_version(container, object,
316
                                                       self.version, **args)
317
        else:
318
            data = self.client.retrieve_object(container, object, **args)    
319
        
320
        f = self.file and open(self.file, 'w') or stdout
321
        if self.detail:
322
            data = json.loads(data)
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_true',
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 (\'True\'/\'False\')')
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
            format = 'json' if detail else 'text'
405
            self.client.create_object_by_hashmap(container, object, f, format,
406
                                 meta=meta, **args)
407
        elif self.x_object_manifest:
408
            self.client.create_manifestation(container, object, self.x_object_manifest)
409
        elif not f:
410
            self.client.create_zero_length_object(container, object, meta=meta, **args)
411
        else:
412
            data = f.read()
413
            self.client.create_object(container, object, data, meta=meta, **args)
414
        if f:
415
            f.close()
416

    
417
@cli_command('copy', 'cp')
418
class CopyObject(Command):
419
    syntax = '<src container>/<src object> [<dst container>/]<dst object>'
420
    description = 'copy an object to a different location'
421
    
422
    def add_options(self, parser):
423
        parser.add_option('--version', action='store',
424
                          dest='version', default=False,
425
                          help='copy specific version')
426
        parser.add_option('--public', action='store',
427
                          dest='public', default=None,
428
                          help='publish/unpublish object (\'True\'/\'False\')')
429
    
430
    def execute(self, src, dst):
431
        src_container, sep, src_object = src.partition('/')
432
        dst_container, sep, dst_object = dst.partition('/')
433
        if not sep:
434
            dst_container = src_container
435
            dst_object = dst
436
        version = getattr(self, 'version')
437
        headers = None
438
        if version:
439
            headers = {}
440
            headers['X_SOURCE_VERSION'] = version
441
        if self.public and self.nopublic:
442
            raise Fault('Conflicting options')
443
        if self.public not in ['True', 'False', None]:
444
            raise Fault('Not acceptable value for public')
445
        public = eval(self.public) if self.public else None
446
        self.client.copy_object(src_container, src_object, dst_container,
447
                                dst_object, public, headers)
448

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

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

    
542
@cli_command('move', 'mv')
543
class MoveObject(Command):
544
    syntax = '<src container>/<src object> [<dst container>/]<dst object>'
545
    description = 'move an object to a different location'
546
    
547
    def add_options(self, parser):
548
        parser.add_option('--public', action='store',
549
                          dest='public', default=None,
550
                          help='publish/unpublish object (\'True\'/\'False\')')
551
    
552
    def execute(self, src, dst):
553
        src_container, sep, src_object = src.partition('/')
554
        dst_container, sep, dst_object = dst.partition('/')
555
        if not sep:
556
            dst_container = src_container
557
            dst_object = dst
558
        if self.public not in ['True', 'False', None]:
559
            raise Fault('Not acceptable value for public')
560
        public = eval(self.public) if self.public else None
561
        self.client.move_object(src_container, src_object, dst_container,
562
                                dst_object, public, headers)
563

    
564
@cli_command('remove')
565
class TrashObject(Command):
566
    syntax = '<container>/<object>'
567
    description = 'trash an object'
568
    
569
    def execute(self, src):
570
        src_container, sep, src_object = src.partition('/')
571
        
572
        self.client.trash_object(src_container, src_object)
573

    
574
@cli_command('restore')
575
class RestoreObject(Command):
576
    syntax = '<container>/<object>'
577
    description = 'restore a trashed object'
578
    
579
    def execute(self, src):
580
        src_container, sep, src_object = src.partition('/')
581
        
582
        self.client.restore_object(src_container, src_object)
583

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

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

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

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

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

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

    
671
def print_usage():
672
    cmd = Command('', [])
673
    parser = cmd.parser
674
    parser.usage = '%prog <command> [options]'
675
    parser.print_help()
676
    
677
    commands = []
678
    for cls in set(_cli_commands.values()):
679
        name = ', '.join(cls.commands)
680
        description = getattr(cls, 'description', '')
681
        commands.append('  %s %s' % (name.ljust(12), description))
682
    print '\nCommands:\n' + '\n'.join(sorted(commands))
683

    
684
def print_dict(d, header='name', f=stdout, detail=True):
685
    header = header in d and header or 'subdir'
686
    if header and header in d:
687
        f.write('%s\n' %d.pop(header))
688
    if detail:
689
        patterns = ['^x_(account|container|object)_meta_(\w+)$']
690
        patterns.append(patterns[0].replace('_', '-'))
691
        for key, val in sorted(d.items()):
692
            f.write('%s: %s\n' % (key.rjust(30), val))
693

    
694
def print_list(l, verbose=False, f=stdout, detail=True):
695
    for elem in l:
696
        #if it's empty string continue
697
        if not elem:
698
            continue
699
        if type(elem) == types.DictionaryType:
700
            print_dict(elem, f=f, detail=detail)
701
        elif type(elem) == types.StringType:
702
            if not verbose:
703
                elem = elem.split('Traceback')[0]
704
            f.write('%s\n' % elem)
705
        else:
706
            f.write('%s\n' % elem)
707

    
708
def print_versions(data, f=stdout):
709
    if 'versions' not in data:
710
        f.write('%s\n' %data)
711
        return
712
    f.write('versions:\n')
713
    for id, t in data['versions']:
714
        f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(t)))
715

    
716
def _get_user():
717
        try:
718
            return os.environ['PITHOS_USER']
719
        except KeyError:
720
            return getuser()
721

    
722
def _get_auth():
723
        try:
724
            return os.environ['PITHOS_AUTH']
725
        except KeyError:
726
            return '0000'
727

    
728
def _get_server():
729
    try:
730
        return os.environ['PITHOS_SERVER']
731
    except KeyError:
732
        return DEFAULT_HOST
733

    
734
def main():
735
    try:
736
        name = argv[1]
737
        cls = class_for_cli_command(name)
738
    except (IndexError, KeyError):
739
        print_usage()
740
        exit(1)
741
    
742
    cmd = cls(name, argv[2:])
743
    
744
    #cmd.execute(*cmd.args)
745
    try:
746
        cmd.execute(*cmd.args)
747
    except TypeError, e:
748
        cmd.parser.print_help()
749
        exit(1)
750
    except Fault, f:
751
        status = f.status and '%s ' % f.status or ''
752
        print '%s%s' % (status, f.data)
753

    
754
if __name__ == '__main__':
755
    main()