Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-tools / pithos / tools / sh.py @ d50ed8d4

History | View | Annotate | Download (33 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.tools.lib.client import Pithos_Client, Fault
43
from pithos.tools.lib.util import get_user, get_auth, get_url
44
from pithos.tools.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

    
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

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

    
68

    
69
class Command(object):
70
    syntax = ''
71

    
72
    def __init__(self, name, argv):
73
        parser = OptionParser('%%prog %s [options] %s' % (name, self.syntax))
74
        parser.add_option('--url', dest='url', metavar='URL',
75
                          default=get_url(), help='server URL (currently: %s)' % get_url())
76
        parser.add_option('--user', dest='user', metavar='USER',
77
                          default=get_user(),
78
                          help='account USER (currently: %s)' % get_user())
79
        parser.add_option('--token', dest='token', metavar='TOKEN',
80
                          default=get_auth(),
81
                          help='account TOKEN (currently: %s)' % get_auth())
82
        parser.add_option('-v', action='store_true', dest='verbose',
83
                          default=False, help='verbose output')
84
        parser.add_option('-d', action='store_true', dest='debug',
85
                          default=False, help='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(
97
            self.url, self.token, self.user, self.verbose,
98
            self.debug)
99

    
100
        self.parser = parser
101
        self.args = args
102

    
103
    def _build_args(self, attrs):
104
        args = {}
105
        for a in [a for a in attrs if getattr(self, a)]:
106
            args[a] = getattr(self, a)
107
        return args
108

    
109
    def add_options(self, parser):
110
        pass
111

    
112
    def execute(self, *args):
113
        pass
114

    
115

    
116
@cli_command('list', 'ls')
117
class List(Command):
118
    syntax = '[<container>[/<object>]]'
119
    description = 'list containers or objects'
120

    
121
    def add_options(self, parser):
122
        parser.add_option('-l', action='store_true', dest='detail',
123
                          default=False, help='show detailed output')
124
        parser.add_option('-n', action='store', type='int', dest='limit',
125
                          default=10000, help='show limited output')
126
        parser.add_option('--marker', action='store', type='str',
127
                          dest='marker', default=None,
128
                          help='show output greater then marker')
129
        parser.add_option('--prefix', action='store', type='str',
130
                          dest='prefix', default=None,
131
                          help='show output starting with prefix')
132
        parser.add_option('--delimiter', action='store', type='str',
133
                          dest='delimiter', default=None,
134
                          help='show output up to the delimiter')
135
        parser.add_option('--path', action='store', type='str',
136
                          dest='path', default=None,
137
                          help='show output starting with prefix up to /')
138
        parser.add_option('--meta', action='store', type='str',
139
                          dest='meta', default=None,
140
                          help='show output having the specified meta keys')
141
        parser.add_option('--if-modified-since', action='store', type='str',
142
                          dest='if_modified_since', default=None,
143
                          help='show output if modified since then')
144
        parser.add_option('--if-unmodified-since', action='store', type='str',
145
                          dest='if_unmodified_since', default=None,
146
                          help='show output if not modified since then')
147
        parser.add_option('--until', action='store', dest='until',
148
                          default=None, help='show metadata until that date')
149
        parser.add_option('--format', action='store', dest='format',
150
                          default='%d/%m/%Y %H:%M:%S', help='format to parse until date (default: %d/%m/%Y %H:%M:%S)')
151
        parser.add_option('--shared', action='store_true', dest='shared',
152
                          default=False, help='show only shared')
153
        parser.add_option('--public', action='store_true', dest='public',
154
                          default=False, help='show only public')
155

    
156
    def execute(self, container=None):
157
        if container:
158
            self.list_objects(container)
159
        else:
160
            self.list_containers()
161

    
162
    def list_containers(self):
163
        attrs = ['limit', 'marker', 'if_modified_since',
164
                 'if_unmodified_since', 'shared', 'public']
165
        args = self._build_args(attrs)
166
        args['format'] = 'json' if self.detail else 'text'
167

    
168
        if getattr(self, 'until'):
169
            t = _time.strptime(self.until, self.format)
170
            args['until'] = int(_time.mktime(t))
171

    
172
        l = self.client.list_containers(**args)
173
        print_list(l)
174

    
175
    def list_objects(self, container):
176
        #prepate params
177
        params = {}
178
        attrs = ['limit', 'marker', 'prefix', 'delimiter', 'path',
179
                 'meta', 'if_modified_since', 'if_unmodified_since',
180
                 'shared', 'public']
181
        args = self._build_args(attrs)
182
        args['format'] = 'json' if self.detail else 'text'
183

    
184
        if self.until:
185
            t = _time.strptime(self.until, self.format)
186
            args['until'] = int(_time.mktime(t))
187

    
188
        container, sep, object = container.partition('/')
189
        if object:
190
            return
191

    
192
        detail = 'json'
193
        #if request with meta quering disable trash filtering
194
        show_trashed = True if self.meta else False
195
        l = self.client.list_objects(container, **args)
196
        print_list(l, detail=self.detail)
197

    
198

    
199
@cli_command('meta')
200
class Meta(Command):
201
    syntax = '[<container>[/<object>]]'
202
    description = 'get account/container/object metadata'
203

    
204
    def add_options(self, parser):
205
        parser.add_option('-r', action='store_true', dest='restricted',
206
                          default=False, help='show only user defined metadata')
207
        parser.add_option('--until', action='store', dest='until',
208
                          default=None, help='show metadata until that date')
209
        parser.add_option('--format', action='store', dest='format',
210
                          default='%d/%m/%Y %H:%M:%S', help='format to parse until date (default: %d/%m/%Y %H:%M:%S)')
211
        parser.add_option('--version', action='store', dest='version',
212
                          default=None, help='show specific version \
213
                                  (applies only for objects)')
214

    
215
    def execute(self, path=''):
216
        container, sep, object = path.partition('/')
217
        args = {'restricted': self.restricted}
218
        if getattr(self, 'until'):
219
            t = _time.strptime(self.until, self.format)
220
            args['until'] = int(_time.mktime(t))
221

    
222
        if object:
223
            meta = self.client.retrieve_object_metadata(container, object,
224
                                                        self.restricted,
225
                                                        self.version)
226
        elif container:
227
            meta = self.client.retrieve_container_metadata(container, **args)
228
        else:
229
            meta = self.client.retrieve_account_metadata(**args)
230
        if meta is None:
231
            print 'Entity does not exist'
232
        else:
233
            print_dict(meta, header=None)
234

    
235

    
236
@cli_command('create')
237
class CreateContainer(Command):
238
    syntax = '<container> [key=val] [...]'
239
    description = 'create a container'
240

    
241
    def add_options(self, parser):
242
        parser.add_option('--versioning', action='store', dest='versioning',
243
                          default=None, help='set container versioning (auto/none)')
244
        parser.add_option('--quota', action='store', dest='quota',
245
                          default=None, help='set default container quota')
246

    
247
    def execute(self, container, *args):
248
        meta = {}
249
        for arg in args:
250
            key, sep, val = arg.partition('=')
251
            meta[key] = val
252
        policy = {}
253
        if getattr(self, 'versioning'):
254
            policy['versioning'] = self.versioning
255
        if getattr(self, 'quota'):
256
            policy['quota'] = self.quota
257
        ret = self.client.create_container(
258
            container, meta=meta, policies=policy)
259
        if not ret:
260
            print 'Container already exists'
261

    
262

    
263
@cli_command('delete', 'rm')
264
class Delete(Command):
265
    syntax = '<container>[/<object>]'
266
    description = 'delete a container or an object'
267

    
268
    def add_options(self, parser):
269
        parser.add_option('--until', action='store', dest='until',
270
                          default=None, help='remove history until that date')
271
        parser.add_option('--format', action='store', dest='format',
272
                          default='%d/%m/%Y %H:%M:%S', help='format to parse until date (default: %d/%m/%Y %H:%M:%S)')
273
        parser.add_option('--delimiter', action='store', type='str',
274
                          dest='delimiter', default=None,
275
                          help='mass delete objects with path staring with <src object> + delimiter')
276
        parser.add_option('-r', action='store_true',
277
                          dest='recursive', default=False,
278
                          help='mass delimiter objects with delimiter /')
279

    
280
    def execute(self, path):
281
        container, sep, object = path.partition('/')
282
        until = None
283
        if getattr(self, 'until'):
284
            t = _time.strptime(self.until, self.format)
285
            until = int(_time.mktime(t))
286

    
287
        kwargs = {}
288
        if self.delimiter:
289
            kwargs['delimiter'] = self.delimiter
290
        elif self.recursive:
291
            kwargs['delimiter'] = '/'
292

    
293
        if object:
294
            self.client.delete_object(container, object, until, **kwargs)
295
        else:
296
            self.client.delete_container(container, until, **kwargs)
297

    
298

    
299
@cli_command('get')
300
class GetObject(Command):
301
    syntax = '<container>/<object>'
302
    description = 'get the data of an object'
303

    
304
    def add_options(self, parser):
305
        parser.add_option('-l', action='store_true', dest='detail',
306
                          default=False, help='show detailed output')
307
        parser.add_option('--range', action='store', dest='range',
308
                          default=None, help='show range of data')
309
        parser.add_option('--if-range', action='store', dest='if_range',
310
                          default=None, help='show range of data')
311
        parser.add_option('--if-match', action='store', dest='if_match',
312
                          default=None, help='show output if ETags match')
313
        parser.add_option('--if-none-match', action='store',
314
                          dest='if_none_match', default=None,
315
                          help='show output if ETags don\'t match')
316
        parser.add_option('--if-modified-since', action='store', type='str',
317
                          dest='if_modified_since', default=None,
318
                          help='show output if modified since then')
319
        parser.add_option('--if-unmodified-since', action='store', type='str',
320
                          dest='if_unmodified_since', default=None,
321
                          help='show output if not modified since then')
322
        parser.add_option('-o', action='store', type='str',
323
                          dest='file', default=None,
324
                          help='save output in file')
325
        parser.add_option('--version', action='store', type='str',
326
                          dest='version', default=None,
327
                          help='get the specific \
328
                               version')
329
        parser.add_option('--versionlist', action='store_true',
330
                          dest='versionlist', default=False,
331
                          help='get the full object version list')
332
        parser.add_option('--hashmap', action='store_true',
333
                          dest='hashmap', default=False,
334
                          help='get the object hashmap instead')
335

    
336
    def execute(self, path):
337
        attrs = ['if_match', 'if_none_match', 'if_modified_since',
338
                 'if_unmodified_since', 'hashmap']
339
        args = self._build_args(attrs)
340
        args['format'] = 'json' if self.detail else 'text'
341
        if self.range:
342
            args['range'] = 'bytes=%s' % self.range
343
        if getattr(self, 'if_range'):
344
            args['if-range'] = 'If-Range:%s' % getattr(self, 'if_range')
345

    
346
        container, sep, object = path.partition('/')
347
        data = None
348
        if self.versionlist:
349
            if 'detail' in args.keys():
350
                args.pop('detail')
351
            args.pop('format')
352
            self.detail = True
353
            data = self.client.retrieve_object_versionlist(
354
                container, object, **args)
355
        elif self.version:
356
            data = self.client.retrieve_object_version(container, object,
357
                                                       self.version, **args)
358
        elif self.hashmap:
359
            if 'detail' in args.keys():
360
                args.pop('detail')
361
            args.pop('format')
362
            self.detail = True
363
            data = self.client.retrieve_object_hashmap(
364
                container, object, **args)
365
        else:
366
            data = self.client.retrieve_object(container, object, **args)
367

    
368
        f = open(self.file, 'w') if self.file else stdout
369
        if self.detail or isinstance(data, types.DictionaryType):
370
            if self.versionlist:
371
                print_versions(data, f=f)
372
            else:
373
                print_dict(data, f=f)
374
        else:
375
            f.write(data)
376
        f.close()
377

    
378

    
379
@cli_command('mkdir')
380
class PutMarker(Command):
381
    syntax = '<container>/<directory marker>'
382
    description = 'create a directory marker'
383

    
384
    def execute(self, path):
385
        container, sep, object = path.partition('/')
386
        self.client.create_directory_marker(container, object)
387

    
388

    
389
@cli_command('put')
390
class PutObject(Command):
391
    syntax = '<container>/<object> [key=val] [...]'
392
    description = 'create/override object'
393

    
394
    def add_options(self, parser):
395
        parser.add_option(
396
            '--use_hashes', action='store_true', dest='use_hashes',
397
            default=False, help='provide hashmap instead of data')
398
        parser.add_option('--chunked', action='store_true', dest='chunked',
399
                          default=False, help='set chunked transfer mode')
400
        parser.add_option('--etag', action='store', dest='etag',
401
                          default=None, help='check written data')
402
        parser.add_option('--content-encoding', action='store',
403
                          dest='content_encoding', default=None,
404
                          help='provide the object MIME content type')
405
        parser.add_option('--content-disposition', action='store', type='str',
406
                          dest='content_disposition', default=None,
407
                          help='provide the presentation style of the object')
408
        #parser.add_option('-S', action='store',
409
        #                  dest='segment_size', default=False,
410
        #                  help='use for large file support')
411
        parser.add_option('--manifest', action='store',
412
                          dest='x_object_manifest', default=None,
413
                          help='provide object parts prefix in <container>/<object> form')
414
        parser.add_option('--content-type', action='store',
415
                          dest='content_type', default=None,
416
                          help='create object with specific content type')
417
        parser.add_option('--sharing', action='store',
418
                          dest='x_object_sharing', default=None,
419
                          help='define sharing object policy')
420
        parser.add_option('-f', action='store',
421
                          dest='srcpath', default=None,
422
                          help='file descriptor to read from (pass - for standard input)')
423
        parser.add_option('--public', action='store_true',
424
                          dest='x_object_public', default=False,
425
                          help='make object publicly accessible')
426

    
427
    def execute(self, path, *args):
428
        if path.find('=') != -1:
429
            raise Fault('Missing path argument')
430

    
431
        #prepare user defined meta
432
        meta = {}
433
        for arg in args:
434
            key, sep, val = arg.partition('=')
435
            meta[key] = val
436

    
437
        attrs = ['etag', 'content_encoding', 'content_disposition',
438
                 'content_type', 'x_object_sharing', 'x_object_public']
439
        args = self._build_args(attrs)
440

    
441
        container, sep, object = path.partition('/')
442

    
443
        f = None
444
        if self.srcpath:
445
            f = open(self.srcpath) if self.srcpath != '-' else stdin
446

    
447
        if self.use_hashes and not f:
448
            raise Fault('Illegal option combination')
449

    
450
        if self.chunked:
451
            self.client.create_object_using_chunks(container, object, f,
452
                                                   meta=meta, **args)
453
        elif self.use_hashes:
454
            data = f.read()
455
            hashmap = json.loads(data)
456
            self.client.create_object_by_hashmap(container, object, hashmap,
457
                                                 meta=meta, **args)
458
        elif self.x_object_manifest:
459
            self.client.create_manifestation(
460
                container, object, self.x_object_manifest)
461
        elif not f:
462
            self.client.create_zero_length_object(
463
                container, object, meta=meta, **args)
464
        else:
465
            self.client.create_object(container, object, f, meta=meta, **args)
466
        if f:
467
            f.close()
468

    
469

    
470
@cli_command('copy', 'cp')
471
class CopyObject(Command):
472
    syntax = '<src container>/<src object> [<dst container>/]<dst object> [key=val] [...]'
473
    description = 'copy an object to a different location'
474

    
475
    def add_options(self, parser):
476
        parser.add_option('--version', action='store',
477
                          dest='version', default=False,
478
                          help='copy specific version')
479
        parser.add_option('--public', action='store_true',
480
                          dest='public', default=False,
481
                          help='make object publicly accessible')
482
        parser.add_option('--content-type', action='store',
483
                          dest='content_type', default=None,
484
                          help='change object\'s content type')
485
        parser.add_option('--delimiter', action='store', type='str',
486
                          dest='delimiter', default=None,
487
                          help='mass copy objects with path staring with <src object> + delimiter')
488
        parser.add_option('-r', action='store_true',
489
                          dest='recursive', default=False,
490
                          help='mass copy with delimiter /')
491

    
492
    def execute(self, src, dst, *args):
493
        src_container, sep, src_object = src.partition('/')
494
        dst_container, sep, dst_object = dst.partition('/')
495

    
496
        #prepare user defined meta
497
        meta = {}
498
        for arg in args:
499
            key, sep, val = arg.partition('=')
500
            meta[key] = val
501

    
502
        if not sep:
503
            dst_container = src_container
504
            dst_object = dst
505

    
506
        args = {'content_type': self.content_type} if self.content_type else {}
507
        if self.delimiter:
508
            args['delimiter'] = self.delimiter
509
        elif self.recursive:
510
            args['delimiter'] = '/'
511
        self.client.copy_object(src_container, src_object, dst_container,
512
                                dst_object, meta, self.public, self.version,
513
                                **args)
514

    
515

    
516
@cli_command('set')
517
class SetMeta(Command):
518
    syntax = '[<container>[/<object>]] key=val [key=val] [...]'
519
    description = 'set account/container/object metadata'
520

    
521
    def execute(self, path, *args):
522
        #in case of account fix the args
523
        if path.find('=') != -1:
524
            args = list(args)
525
            args.append(path)
526
            args = tuple(args)
527
            path = ''
528
        meta = {}
529
        for arg in args:
530
            key, sep, val = arg.partition('=')
531
            meta[key.strip()] = val.strip()
532
        container, sep, object = path.partition('/')
533
        if object:
534
            self.client.update_object_metadata(container, object, **meta)
535
        elif container:
536
            self.client.update_container_metadata(container, **meta)
537
        else:
538
            self.client.update_account_metadata(**meta)
539

    
540

    
541
@cli_command('update')
542
class UpdateObject(Command):
543
    syntax = '<container>/<object> path [key=val] [...]'
544
    description = 'update object metadata/data (default mode: append)'
545

    
546
    def add_options(self, parser):
547
        parser.add_option('-a', action='store_true', dest='append',
548
                          default=True, help='append data')
549
        parser.add_option('--offset', action='store',
550
                          dest='offset',
551
                          default=None, help='starting offest to be updated')
552
        parser.add_option('--range', action='store', dest='content_range',
553
                          default=None, help='range of data to be updated')
554
        parser.add_option('--chunked', action='store_true', dest='chunked',
555
                          default=False, help='set chunked transfer mode')
556
        parser.add_option('--content-encoding', action='store',
557
                          dest='content_encoding', default=None,
558
                          help='provide the object MIME content type')
559
        parser.add_option('--content-disposition', action='store', type='str',
560
                          dest='content_disposition', default=None,
561
                          help='provide the presentation style of the object')
562
        parser.add_option('--manifest', action='store', type='str',
563
                          dest='x_object_manifest', default=None,
564
                          help='use for large file support')
565
        parser.add_option('--sharing', action='store',
566
                          dest='x_object_sharing', default=None,
567
                          help='define sharing object policy')
568
        parser.add_option('--nosharing', action='store_true',
569
                          dest='no_sharing', default=None,
570
                          help='clear object sharing policy')
571
        parser.add_option('-f', action='store',
572
                          dest='srcpath', default=None,
573
                          help='file descriptor to read from: pass - for standard input')
574
        parser.add_option('--public', action='store_true',
575
                          dest='x_object_public', default=False,
576
                          help='make object publicly accessible')
577
        parser.add_option('--replace', action='store_true',
578
                          dest='replace', default=False,
579
                          help='override metadata')
580

    
581
    def execute(self, path, *args):
582
        if path.find('=') != -1:
583
            raise Fault('Missing path argument')
584

    
585
        #prepare user defined meta
586
        meta = {}
587
        for arg in args:
588
            key, sep, val = arg.partition('=')
589
            meta[key] = val
590

    
591
        attrs = ['content_encoding', 'content_disposition', 'x_object_sharing',
592
                 'x_object_public', 'x_object_manifest', 'replace', 'offset',
593
                 'content_range']
594
        args = self._build_args(attrs)
595

    
596
        if self.no_sharing:
597
            args['x_object_sharing'] = ''
598

    
599
        container, sep, object = path.partition('/')
600

    
601
        f = None
602
        if self.srcpath:
603
            f = open(self.srcpath) if self.srcpath != '-' else stdin
604

    
605
        if self.chunked:
606
            self.client.update_object_using_chunks(container, object, f,
607
                                                   meta=meta, **args)
608
        else:
609
            self.client.update_object(container, object, f, meta=meta, **args)
610
        if f:
611
            f.close()
612

    
613

    
614
@cli_command('move', 'mv')
615
class MoveObject(Command):
616
    syntax = '<src container>/<src object> [<dst container>/]<dst object>'
617
    description = 'move an object to a different location'
618

    
619
    def add_options(self, parser):
620
        parser.add_option('--public', action='store_true',
621
                          dest='public', default=False,
622
                          help='make object publicly accessible')
623
        parser.add_option('--content-type', action='store',
624
                          dest='content_type', default=None,
625
                          help='change object\'s content type')
626
        parser.add_option('--delimiter', action='store', type='str',
627
                          dest='delimiter', default=None,
628
                          help='mass move objects with path staring with <src object> + delimiter')
629
        parser.add_option('-r', action='store_true',
630
                          dest='recursive', default=False,
631
                          help='mass move objects with delimiter /')
632

    
633
    def execute(self, src, dst, *args):
634
        src_container, sep, src_object = src.partition('/')
635
        dst_container, sep, dst_object = dst.partition('/')
636
        if not sep:
637
            dst_container = src_container
638
            dst_object = dst
639

    
640
        #prepare user defined meta
641
        meta = {}
642
        for arg in args:
643
            key, sep, val = arg.partition('=')
644
            meta[key] = val
645

    
646
        args = {'content_type': self.content_type} if self.content_type else {}
647
        if self.delimiter:
648
            args['delimiter'] = self.delimiter
649
        elif self.recursive:
650
            args['delimiter'] = '/'
651
        self.client.move_object(src_container, src_object, dst_container,
652
                                dst_object, meta, self.public, **args)
653

    
654

    
655
@cli_command('unset')
656
class UnsetObject(Command):
657
    syntax = '<container>/[<object>] key [key] [...]'
658
    description = 'delete metadata info'
659

    
660
    def execute(self, path, *args):
661
        #in case of account fix the args
662
        if len(args) == 0:
663
            args = list(args)
664
            args.append(path)
665
            args = tuple(args)
666
            path = ''
667
        meta = []
668
        for key in args:
669
            meta.append(key)
670
        container, sep, object = path.partition('/')
671
        if object:
672
            self.client.delete_object_metadata(container, object, meta)
673
        elif container:
674
            self.client.delete_container_metadata(container, meta)
675
        else:
676
            self.client.delete_account_metadata(meta)
677

    
678

    
679
@cli_command('group')
680
class CreateGroup(Command):
681
    syntax = 'key=val [key=val] [...]'
682
    description = 'create account groups'
683

    
684
    def execute(self, *args):
685
        groups = {}
686
        for arg in args:
687
            key, sep, val = arg.partition('=')
688
            groups[key] = val
689
        self.client.set_account_groups(**groups)
690

    
691

    
692
@cli_command('ungroup')
693
class DeleteGroup(Command):
694
    syntax = 'key [key] [...]'
695
    description = 'delete account groups'
696

    
697
    def execute(self, *args):
698
        groups = []
699
        for arg in args:
700
            groups.append(arg)
701
        self.client.unset_account_groups(groups)
702

    
703

    
704
@cli_command('policy')
705
class SetPolicy(Command):
706
    syntax = 'container key=val [key=val] [...]'
707
    description = 'set container policies'
708

    
709
    def execute(self, path, *args):
710
        if path.find('=') != -1:
711
            raise Fault('Missing container argument')
712

    
713
        container, sep, object = path.partition('/')
714

    
715
        if object:
716
            raise Fault('Only containers have policies')
717

    
718
        policies = {}
719
        for arg in args:
720
            key, sep, val = arg.partition('=')
721
            policies[key] = val
722

    
723
        self.client.set_container_policies(container, **policies)
724

    
725

    
726
@cli_command('publish')
727
class PublishObject(Command):
728
    syntax = '<container>/<object>'
729
    description = 'publish an object'
730

    
731
    def execute(self, src):
732
        src_container, sep, src_object = src.partition('/')
733

    
734
        self.client.publish_object(src_container, src_object)
735

    
736

    
737
@cli_command('unpublish')
738
class UnpublishObject(Command):
739
    syntax = '<container>/<object>'
740
    description = 'unpublish an object'
741

    
742
    def execute(self, src):
743
        src_container, sep, src_object = src.partition('/')
744

    
745
        self.client.unpublish_object(src_container, src_object)
746

    
747

    
748
@cli_command('sharing')
749
class SharingObject(Command):
750
    syntax = 'list users sharing objects with the user'
751
    description = 'list user accounts sharing objects with the user'
752

    
753
    def add_options(self, parser):
754
        parser.add_option('-l', action='store_true', dest='detail',
755
                          default=False, help='show detailed output')
756
        parser.add_option('-n', action='store', type='int', dest='limit',
757
                          default=10000, help='show limited output')
758
        parser.add_option('--marker', action='store', type='str',
759
                          dest='marker', default=None,
760
                          help='show output greater then marker')
761

    
762
    def execute(self):
763
        attrs = ['limit', 'marker']
764
        args = self._build_args(attrs)
765
        args['format'] = 'json' if self.detail else 'text'
766

    
767
        print_list(self.client.list_shared_by_others(**args))
768

    
769

    
770
@cli_command('send')
771
class Send(Command):
772
    syntax = '<file> <container>[/<prefix>]'
773
    description = 'upload file to container (using prefix)'
774

    
775
    def execute(self, file, path):
776
        container, sep, prefix = path.partition('/')
777
        upload(self.client, file, container, prefix)
778

    
779

    
780
@cli_command('receive')
781
class Receive(Command):
782
    syntax = '<container>/<object> <file>'
783
    description = 'download object to file'
784

    
785
    def execute(self, path, file):
786
        container, sep, object = path.partition('/')
787
        download(self.client, container, object, file)
788

    
789

    
790
def print_usage():
791
    cmd = Command('', [])
792
    parser = cmd.parser
793
    parser.usage = '%prog <command> [options]'
794
    parser.print_help()
795

    
796
    commands = []
797
    for cls in set(_cli_commands.values()):
798
        name = ', '.join(cls.commands)
799
        description = getattr(cls, 'description', '')
800
        commands.append('  %s %s' % (name.ljust(12), description))
801
    print '\nCommands:\n' + '\n'.join(sorted(commands))
802

    
803

    
804
def print_dict(d, header='name', f=stdout, detail=True):
805
    header = header if header in d else 'subdir'
806
    if header and header in d:
807
        f.write('%s\n' % d.pop(header).encode('utf8'))
808
    if detail:
809
        patterns = ['^x_(account|container|object)_meta_(\w+)$']
810
        patterns.append(patterns[0].replace('_', '-'))
811
        for key, val in sorted(d.items()):
812
            f.write('%s: %s\n' % (key.rjust(30), val))
813

    
814

    
815
def print_list(l, verbose=False, f=stdout, detail=True):
816
    for elem in l:
817
        #if it's empty string continue
818
        if not elem:
819
            continue
820
        if isinstance(elem, types.DictionaryType):
821
            print_dict(elem, f=f, detail=detail)
822
        elif isinstance(elem, types.StringType):
823
            if not verbose:
824
                elem = elem.split('Traceback')[0]
825
            f.write('%s\n' % elem)
826
        else:
827
            f.write('%s\n' % elem)
828

    
829

    
830
def print_versions(data, f=stdout):
831
    if 'versions' not in data:
832
        f.write('%s\n' % data)
833
        return
834
    f.write('versions:\n')
835
    for id, t in data['versions']:
836
        f.write('%s @ %s\n' % (str(id).rjust(30),
837
                datetime.fromtimestamp(float(t))))
838

    
839

    
840
def main():
841
    try:
842
        name = argv[1]
843
        cls = class_for_cli_command(name)
844
    except (IndexError, KeyError):
845
        print_usage()
846
        exit(1)
847

    
848
    cmd = cls(name, argv[2:])
849

    
850
    try:
851
        cmd.execute(*cmd.args)
852
    except TypeError, e:
853
        cmd.parser.print_help()
854
        exit(1)
855
    except Fault, f:
856
        status = '%s ' % f.status if f.status else ''
857
        print '%s%s' % (status, f.data)
858

    
859

    
860
if __name__ == '__main__':
861
    main()