Statistics
| Branch: | Tag: | Revision:

root / tools / store @ f0eacc2c

History | View | Annotate | Download (29.1 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
        else:
410
            data = f.read() if f else None
411
            self.client.create_object(container, object, data, meta=meta, **args)
412
        if f:
413
            f.close()
414

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
752
if __name__ == '__main__':
753
    main()