Statistics
| Branch: | Tag: | Revision:

root / contrib / snf-pithos-tools / pithos / tools / sh.py @ 04a1b675

History | View | Annotate | Download (33.3 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright 2011-2012 GRNET S.A. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
#
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
#
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
#
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

    
36
from getpass import getuser
37
from optparse import OptionParser
38
from os import environ
39
from sys import argv, exit, stdin, stdout
40
from datetime import datetime
41

    
42
from django.core.validators import email_re
43

    
44
from pithos.tools.lib.client import Pithos_Client, Fault
45
from pithos.tools.lib.util import get_user, get_auth, get_url
46
from pithos.tools.lib.transfer import upload, download
47

    
48
import json
49
import logging
50
import types
51
import re
52
import time as _time
53
import os
54

    
55
_cli_commands = {}
56

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

    
65

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

    
69

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

    
73
    def __init__(self, name, argv):
74
        parser = OptionParser('%%prog %s [options] %s' % (name, self.syntax))
75
        parser.add_option('--url', dest='url', metavar='URL',
76
                          default=get_url(), help='server URL (currently: %s)' % get_url())
77
        parser.add_option('--user', dest='user', metavar='USER',
78
                          default=get_user(),
79
                          help='account USER (currently: %s)' % get_user())
80
        parser.add_option('--token', dest='token', metavar='TOKEN',
81
                          default=get_auth(),
82
                          help='account TOKEN (currently: %s)' % get_auth())
83
        parser.add_option('-v', action='store_true', dest='verbose',
84
                          default=False, help='verbose output')
85
        parser.add_option('-d', action='store_true', dest='debug',
86
                          default=False, help='debug output')
87
        self.add_options(parser)
88
        options, args = parser.parse_args(argv)
89

    
90
        # Add options to self
91
        for opt in parser.option_list:
92
            key = opt.dest
93
            if key:
94
                val = getattr(options, key)
95
                setattr(self, key, val)
96

    
97
        if email_re.match(self.user):
98
            try:
99
                from snf_django.lib.astakos import get_user_uuid
100
                from pithos.api.settings import SERVICE_TOKEN
101
                self.user = get_user_uuid(SERVICE_TOKEN, self.user)
102
            except ImportError:
103
                pass
104
        self.client = Pithos_Client(
105
            self.url, self.token, self.user, self.verbose,
106
            self.debug)
107

    
108
        self.parser = parser
109
        self.args = args
110

    
111
    def _build_args(self, attrs):
112
        args = {}
113
        for a in [a for a in attrs if getattr(self, a)]:
114
            args[a] = getattr(self, a)
115
        return args
116

    
117
    def add_options(self, parser):
118
        pass
119

    
120
    def execute(self, *args):
121
        pass
122

    
123

    
124
@cli_command('list', 'ls')
125
class List(Command):
126
    syntax = '[<container>[/<object>]]'
127
    description = 'list containers or objects'
128

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

    
164
    def execute(self, container=None):
165
        if container:
166
            self.list_objects(container)
167
        else:
168
            self.list_containers()
169

    
170
    def list_containers(self):
171
        attrs = ['limit', 'marker', 'if_modified_since',
172
                 'if_unmodified_since', 'shared', 'public']
173
        args = self._build_args(attrs)
174
        args['format'] = 'json' if self.detail else 'text'
175

    
176
        if getattr(self, 'until'):
177
            t = _time.strptime(self.until, self.format)
178
            args['until'] = int(_time.mktime(t))
179

    
180
        l = self.client.list_containers(**args)
181
        print_list(l)
182

    
183
    def list_objects(self, container):
184
        #prepate params
185
        params = {}
186
        attrs = ['limit', 'marker', 'prefix', 'delimiter', 'path',
187
                 'meta', 'if_modified_since', 'if_unmodified_since',
188
                 'shared', 'public']
189
        args = self._build_args(attrs)
190
        args['format'] = 'json' if self.detail else 'text'
191

    
192
        if self.until:
193
            t = _time.strptime(self.until, self.format)
194
            args['until'] = int(_time.mktime(t))
195

    
196
        container, sep, object = container.partition('/')
197
        if object:
198
            return
199

    
200
        detail = 'json'
201
        #if request with meta quering disable trash filtering
202
        show_trashed = True if self.meta else False
203
        l = self.client.list_objects(container, **args)
204
        print_list(l, detail=self.detail)
205

    
206

    
207
@cli_command('meta')
208
class Meta(Command):
209
    syntax = '[<container>[/<object>]]'
210
    description = 'get account/container/object metadata'
211

    
212
    def add_options(self, parser):
213
        parser.add_option('-r', action='store_true', dest='restricted',
214
                          default=False, help='show only user defined metadata')
215
        parser.add_option('--until', action='store', dest='until',
216
                          default=None, help='show metadata until that date')
217
        parser.add_option('--format', action='store', dest='format',
218
                          default='%d/%m/%Y %H:%M:%S', help='format to parse until date (default: %d/%m/%Y %H:%M:%S)')
219
        parser.add_option('--version', action='store', dest='version',
220
                          default=None, help='show specific version \
221
                                  (applies only for objects)')
222

    
223
    def execute(self, path=''):
224
        container, sep, object = path.partition('/')
225
        args = {'restricted': self.restricted}
226
        if getattr(self, 'until'):
227
            t = _time.strptime(self.until, self.format)
228
            args['until'] = int(_time.mktime(t))
229

    
230
        if object:
231
            meta = self.client.retrieve_object_metadata(container, object,
232
                                                        self.restricted,
233
                                                        self.version)
234
        elif container:
235
            meta = self.client.retrieve_container_metadata(container, **args)
236
        else:
237
            meta = self.client.retrieve_account_metadata(**args)
238
        if meta is None:
239
            print 'Entity does not exist'
240
        else:
241
            print_dict(meta, header=None)
242

    
243

    
244
@cli_command('create')
245
class CreateContainer(Command):
246
    syntax = '<container> [key=val] [...]'
247
    description = 'create a container'
248

    
249
    def add_options(self, parser):
250
        parser.add_option('--versioning', action='store', dest='versioning',
251
                          default=None, help='set container versioning (auto/none)')
252
        parser.add_option('--quota', action='store', dest='quota',
253
                          default=None, help='set default container quota')
254

    
255
    def execute(self, container, *args):
256
        meta = {}
257
        for arg in args:
258
            key, sep, val = arg.partition('=')
259
            meta[key] = val
260
        policy = {}
261
        if getattr(self, 'versioning'):
262
            policy['versioning'] = self.versioning
263
        if getattr(self, 'quota'):
264
            policy['quota'] = self.quota
265
        ret = self.client.create_container(
266
            container, meta=meta, policies=policy)
267
        if not ret:
268
            print 'Container already exists'
269

    
270

    
271
@cli_command('delete', 'rm')
272
class Delete(Command):
273
    syntax = '<container>[/<object>]'
274
    description = 'delete a container or an object'
275

    
276
    def add_options(self, parser):
277
        parser.add_option('--until', action='store', dest='until',
278
                          default=None, help='remove history until that date')
279
        parser.add_option('--format', action='store', dest='format',
280
                          default='%d/%m/%Y %H:%M:%S', help='format to parse until date (default: %d/%m/%Y %H:%M:%S)')
281
        parser.add_option('--delimiter', action='store', type='str',
282
                          dest='delimiter', default=None,
283
                          help='mass delete objects with path staring with <src object> + delimiter')
284
        parser.add_option('-r', action='store_true',
285
                          dest='recursive', default=False,
286
                          help='mass delimiter objects with delimiter /')
287

    
288
    def execute(self, path):
289
        container, sep, object = path.partition('/')
290
        until = None
291
        if getattr(self, 'until'):
292
            t = _time.strptime(self.until, self.format)
293
            until = int(_time.mktime(t))
294

    
295
        kwargs = {}
296
        if self.delimiter:
297
            kwargs['delimiter'] = self.delimiter
298
        elif self.recursive:
299
            kwargs['delimiter'] = '/'
300

    
301
        if object:
302
            self.client.delete_object(container, object, until, **kwargs)
303
        else:
304
            self.client.delete_container(container, until, **kwargs)
305

    
306

    
307
@cli_command('get')
308
class GetObject(Command):
309
    syntax = '<container>/<object>'
310
    description = 'get the data of an object'
311

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

    
344
    def execute(self, path):
345
        attrs = ['if_match', 'if_none_match', 'if_modified_since',
346
                 'if_unmodified_since', 'hashmap']
347
        args = self._build_args(attrs)
348
        args['format'] = 'json' if self.detail else 'text'
349
        if self.range:
350
            args['range'] = 'bytes=%s' % self.range
351
        if getattr(self, 'if_range'):
352
            args['if-range'] = 'If-Range:%s' % getattr(self, 'if_range')
353

    
354
        container, sep, object = path.partition('/')
355
        data = None
356
        if self.versionlist:
357
            if 'detail' in args.keys():
358
                args.pop('detail')
359
            args.pop('format')
360
            self.detail = True
361
            data = self.client.retrieve_object_versionlist(
362
                container, object, **args)
363
        elif self.version:
364
            data = self.client.retrieve_object_version(container, object,
365
                                                       self.version, **args)
366
        elif self.hashmap:
367
            if 'detail' in args.keys():
368
                args.pop('detail')
369
            args.pop('format')
370
            self.detail = True
371
            data = self.client.retrieve_object_hashmap(
372
                container, object, **args)
373
        else:
374
            data = self.client.retrieve_object(container, object, **args)
375

    
376
        f = open(self.file, 'w') if self.file else stdout
377
        if self.detail or isinstance(data, types.DictionaryType):
378
            if self.versionlist:
379
                print_versions(data, f=f)
380
            else:
381
                print_dict(data, f=f)
382
        else:
383
            f.write(data)
384
        f.close()
385

    
386

    
387
@cli_command('mkdir')
388
class PutMarker(Command):
389
    syntax = '<container>/<directory marker>'
390
    description = 'create a directory marker'
391

    
392
    def execute(self, path):
393
        container, sep, object = path.partition('/')
394
        self.client.create_directory_marker(container, object)
395

    
396

    
397
@cli_command('put')
398
class PutObject(Command):
399
    syntax = '<container>/<object> [key=val] [...]'
400
    description = 'create/override object'
401

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

    
435
    def execute(self, path, *args):
436
        if path.find('=') != -1:
437
            raise Fault('Missing path argument')
438

    
439
        #prepare user defined meta
440
        meta = {}
441
        for arg in args:
442
            key, sep, val = arg.partition('=')
443
            meta[key] = val
444

    
445
        attrs = ['etag', 'content_encoding', 'content_disposition',
446
                 'content_type', 'x_object_sharing', 'x_object_public']
447
        args = self._build_args(attrs)
448

    
449
        container, sep, object = path.partition('/')
450

    
451
        f = None
452
        if self.srcpath:
453
            f = open(self.srcpath) if self.srcpath != '-' else stdin
454

    
455
        if self.use_hashes and not f:
456
            raise Fault('Illegal option combination')
457

    
458
        if self.chunked:
459
            self.client.create_object_using_chunks(container, object, f,
460
                                                   meta=meta, **args)
461
        elif self.use_hashes:
462
            data = f.read()
463
            hashmap = json.loads(data)
464
            self.client.create_object_by_hashmap(container, object, hashmap,
465
                                                 meta=meta, **args)
466
        elif self.x_object_manifest:
467
            self.client.create_manifestation(
468
                container, object, self.x_object_manifest)
469
        elif not f:
470
            self.client.create_zero_length_object(
471
                container, object, meta=meta, **args)
472
        else:
473
            self.client.create_object(container, object, f, meta=meta, **args)
474
        if f:
475
            f.close()
476

    
477

    
478
@cli_command('copy', 'cp')
479
class CopyObject(Command):
480
    syntax = '<src container>/<src object> [<dst container>/]<dst object> [key=val] [...]'
481
    description = 'copy an object to a different location'
482

    
483
    def add_options(self, parser):
484
        parser.add_option('--version', action='store',
485
                          dest='version', default=False,
486
                          help='copy specific version')
487
        parser.add_option('--public', action='store_true',
488
                          dest='public', default=False,
489
                          help='make object publicly accessible')
490
        parser.add_option('--content-type', action='store',
491
                          dest='content_type', default=None,
492
                          help='change object\'s content type')
493
        parser.add_option('--delimiter', action='store', type='str',
494
                          dest='delimiter', default=None,
495
                          help='mass copy objects with path staring with <src object> + delimiter')
496
        parser.add_option('-r', action='store_true',
497
                          dest='recursive', default=False,
498
                          help='mass copy with delimiter /')
499

    
500
    def execute(self, src, dst, *args):
501
        src_container, sep, src_object = src.partition('/')
502
        dst_container, sep, dst_object = dst.partition('/')
503

    
504
        #prepare user defined meta
505
        meta = {}
506
        for arg in args:
507
            key, sep, val = arg.partition('=')
508
            meta[key] = val
509

    
510
        if not sep:
511
            dst_container = src_container
512
            dst_object = dst
513

    
514
        args = {'content_type': self.content_type} if self.content_type else {}
515
        if self.delimiter:
516
            args['delimiter'] = self.delimiter
517
        elif self.recursive:
518
            args['delimiter'] = '/'
519
        self.client.copy_object(src_container, src_object, dst_container,
520
                                dst_object, meta, self.public, self.version,
521
                                **args)
522

    
523

    
524
@cli_command('set')
525
class SetMeta(Command):
526
    syntax = '[<container>[/<object>]] key=val [key=val] [...]'
527
    description = 'set account/container/object metadata'
528

    
529
    def execute(self, path, *args):
530
        #in case of account fix the args
531
        if path.find('=') != -1:
532
            args = list(args)
533
            args.append(path)
534
            args = tuple(args)
535
            path = ''
536
        meta = {}
537
        for arg in args:
538
            key, sep, val = arg.partition('=')
539
            meta[key.strip()] = val.strip()
540
        container, sep, object = path.partition('/')
541
        if object:
542
            self.client.update_object_metadata(container, object, **meta)
543
        elif container:
544
            self.client.update_container_metadata(container, **meta)
545
        else:
546
            self.client.update_account_metadata(**meta)
547

    
548

    
549
@cli_command('update')
550
class UpdateObject(Command):
551
    syntax = '<container>/<object> path [key=val] [...]'
552
    description = 'update object metadata/data (default mode: append)'
553

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

    
589
    def execute(self, path, *args):
590
        if path.find('=') != -1:
591
            raise Fault('Missing path argument')
592

    
593
        #prepare user defined meta
594
        meta = {}
595
        for arg in args:
596
            key, sep, val = arg.partition('=')
597
            meta[key] = val
598

    
599
        attrs = ['content_encoding', 'content_disposition', 'x_object_sharing',
600
                 'x_object_public', 'x_object_manifest', 'replace', 'offset',
601
                 'content_range']
602
        args = self._build_args(attrs)
603

    
604
        if self.no_sharing:
605
            args['x_object_sharing'] = ''
606

    
607
        container, sep, object = path.partition('/')
608

    
609
        f = None
610
        if self.srcpath:
611
            f = open(self.srcpath) if self.srcpath != '-' else stdin
612

    
613
        if self.chunked:
614
            self.client.update_object_using_chunks(container, object, f,
615
                                                   meta=meta, **args)
616
        else:
617
            self.client.update_object(container, object, f, meta=meta, **args)
618
        if f:
619
            f.close()
620

    
621

    
622
@cli_command('move', 'mv')
623
class MoveObject(Command):
624
    syntax = '<src container>/<src object> [<dst container>/]<dst object>'
625
    description = 'move an object to a different location'
626

    
627
    def add_options(self, parser):
628
        parser.add_option('--public', action='store_true',
629
                          dest='public', default=False,
630
                          help='make object publicly accessible')
631
        parser.add_option('--content-type', action='store',
632
                          dest='content_type', default=None,
633
                          help='change object\'s content type')
634
        parser.add_option('--delimiter', action='store', type='str',
635
                          dest='delimiter', default=None,
636
                          help='mass move objects with path staring with <src object> + delimiter')
637
        parser.add_option('-r', action='store_true',
638
                          dest='recursive', default=False,
639
                          help='mass move objects with delimiter /')
640

    
641
    def execute(self, src, dst, *args):
642
        src_container, sep, src_object = src.partition('/')
643
        dst_container, sep, dst_object = dst.partition('/')
644
        if not sep:
645
            dst_container = src_container
646
            dst_object = dst
647

    
648
        #prepare user defined meta
649
        meta = {}
650
        for arg in args:
651
            key, sep, val = arg.partition('=')
652
            meta[key] = val
653

    
654
        args = {'content_type': self.content_type} if self.content_type else {}
655
        if self.delimiter:
656
            args['delimiter'] = self.delimiter
657
        elif self.recursive:
658
            args['delimiter'] = '/'
659
        self.client.move_object(src_container, src_object, dst_container,
660
                                dst_object, meta, self.public, **args)
661

    
662

    
663
@cli_command('unset')
664
class UnsetObject(Command):
665
    syntax = '<container>/[<object>] key [key] [...]'
666
    description = 'delete metadata info'
667

    
668
    def execute(self, path, *args):
669
        #in case of account fix the args
670
        if len(args) == 0:
671
            args = list(args)
672
            args.append(path)
673
            args = tuple(args)
674
            path = ''
675
        meta = []
676
        for key in args:
677
            meta.append(key)
678
        container, sep, object = path.partition('/')
679
        if object:
680
            self.client.delete_object_metadata(container, object, meta)
681
        elif container:
682
            self.client.delete_container_metadata(container, meta)
683
        else:
684
            self.client.delete_account_metadata(meta)
685

    
686

    
687
@cli_command('group')
688
class CreateGroup(Command):
689
    syntax = 'key=val [key=val] [...]'
690
    description = 'create account groups'
691

    
692
    def execute(self, *args):
693
        groups = {}
694
        for arg in args:
695
            key, sep, val = arg.partition('=')
696
            groups[key] = val
697
        self.client.set_account_groups(**groups)
698

    
699

    
700
@cli_command('ungroup')
701
class DeleteGroup(Command):
702
    syntax = 'key [key] [...]'
703
    description = 'delete account groups'
704

    
705
    def execute(self, *args):
706
        groups = []
707
        for arg in args:
708
            groups.append(arg)
709
        self.client.unset_account_groups(groups)
710

    
711

    
712
@cli_command('policy')
713
class SetPolicy(Command):
714
    syntax = 'container key=val [key=val] [...]'
715
    description = 'set container policies'
716

    
717
    def execute(self, path, *args):
718
        if path.find('=') != -1:
719
            raise Fault('Missing container argument')
720

    
721
        container, sep, object = path.partition('/')
722

    
723
        if object:
724
            raise Fault('Only containers have policies')
725

    
726
        policies = {}
727
        for arg in args:
728
            key, sep, val = arg.partition('=')
729
            policies[key] = val
730

    
731
        self.client.set_container_policies(container, **policies)
732

    
733

    
734
@cli_command('publish')
735
class PublishObject(Command):
736
    syntax = '<container>/<object>'
737
    description = 'publish an object'
738

    
739
    def execute(self, src):
740
        src_container, sep, src_object = src.partition('/')
741

    
742
        self.client.publish_object(src_container, src_object)
743

    
744

    
745
@cli_command('unpublish')
746
class UnpublishObject(Command):
747
    syntax = '<container>/<object>'
748
    description = 'unpublish an object'
749

    
750
    def execute(self, src):
751
        src_container, sep, src_object = src.partition('/')
752

    
753
        self.client.unpublish_object(src_container, src_object)
754

    
755

    
756
@cli_command('sharing')
757
class SharingObject(Command):
758
    syntax = 'list users sharing objects with the user'
759
    description = 'list user accounts sharing objects with the user'
760

    
761
    def add_options(self, parser):
762
        parser.add_option('-l', action='store_true', dest='detail',
763
                          default=False, help='show detailed output')
764
        parser.add_option('-n', action='store', type='int', dest='limit',
765
                          default=10000, help='show limited output')
766
        parser.add_option('--marker', action='store', type='str',
767
                          dest='marker', default=None,
768
                          help='show output greater then marker')
769

    
770
    def execute(self):
771
        attrs = ['limit', 'marker']
772
        args = self._build_args(attrs)
773
        args['format'] = 'json' if self.detail else 'text'
774
        args['translate'] = ''
775

    
776
        print_list(self.client.list_shared_with_me(**args))
777

    
778

    
779
@cli_command('send')
780
class Send(Command):
781
    syntax = '<file> <container>[/<prefix>]'
782
    description = 'upload file to container (using prefix)'
783

    
784
    def execute(self, file, path):
785
        container, sep, prefix = path.partition('/')
786
        upload(self.client, file, container, prefix)
787

    
788

    
789
@cli_command('receive')
790
class Receive(Command):
791
    syntax = '<container>/<object> <file>'
792
    description = 'download object to file'
793

    
794
    def execute(self, path, file):
795
        container, sep, object = path.partition('/')
796
        download(self.client, container, object, file)
797

    
798

    
799
def print_usage():
800
    cmd = Command('', [])
801
    parser = cmd.parser
802
    parser.usage = '%prog <command> [options]'
803
    parser.print_help()
804

    
805
    commands = []
806
    for cls in set(_cli_commands.values()):
807
        name = ', '.join(cls.commands)
808
        description = getattr(cls, 'description', '')
809
        commands.append('  %s %s' % (name.ljust(12), description))
810
    print '\nCommands:\n' + '\n'.join(sorted(commands))
811

    
812

    
813
def print_dict(d, header='name', f=stdout, detail=True):
814
    header = header if header in d else 'subdir'
815
    if header and header in d:
816
        f.write('%s\n' % d.pop(header).encode('utf8'))
817
    if detail:
818
        patterns = ['^x_(account|container|object)_meta_(\w+)$']
819
        patterns.append(patterns[0].replace('_', '-'))
820
        for key, val in sorted(d.items()):
821
            f.write('%s: %s\n' % (key.rjust(30), val))
822

    
823

    
824
def print_list(l, verbose=False, f=stdout, detail=True):
825
    for elem in l:
826
        #if it's empty string continue
827
        if not elem:
828
            continue
829
        if isinstance(elem, types.DictionaryType):
830
            print_dict(elem, f=f, detail=detail)
831
        elif isinstance(elem, types.StringType):
832
            if not verbose:
833
                elem = elem.split('Traceback')[0]
834
            f.write('%s\n' % elem)
835
        else:
836
            f.write('%s\n' % elem)
837

    
838

    
839
def print_versions(data, f=stdout):
840
    if 'versions' not in data:
841
        f.write('%s\n' % data)
842
        return
843
    f.write('versions:\n')
844
    for id, t in data['versions']:
845
        f.write('%s @ %s\n' % (str(id).rjust(30),
846
                datetime.fromtimestamp(float(t))))
847

    
848

    
849
def main():
850
    try:
851
        name = argv[1]
852
        cls = class_for_cli_command(name)
853
    except (IndexError, KeyError):
854
        print_usage()
855
        exit(1)
856

    
857
    cmd = cls(name, argv[2:])
858

    
859
    try:
860
        cmd.execute(*cmd.args)
861
    except TypeError, e:
862
        cmd.parser.print_help()
863
        exit(1)
864
    except Fault, f:
865
        status = '%s ' % f.status if f.status else ''
866
        print '%s%s' % (status, f.data)
867

    
868

    
869
if __name__ == '__main__':
870
    main()