Fix help message formating, go on with pithos_cli
[kamaki] / kamaki / cli / commands / pithos_cli.py
1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.command
33
34 from kamaki.cli import command#, set_api_description
35 from kamaki.clients.utils import filter_in
36 from kamaki.cli.errors import CLIError, raiseCLIError
37 from kamaki.cli.utils import format_size, print_dict, pretty_keys, print_list
38 from kamaki.cli.argument import FlagArgument, ValueArgument
39 #set_api_description('store', 'Pithos+ storage commands')
40 API_DESCRIPTION = {'store':'Pithos+ storage commands'}
41 from kamaki.clients.pithos import PithosClient, ClientError
42 from colors import bold
43 from sys import stdout, exit
44 import signal
45 from time import localtime, strftime, strptime, mktime
46
47 from progress.bar import IncrementalBar
48
49 class DelimiterArgument(ValueArgument):
50     def __init__(self, caller_obj, help='', parsed_name=None, default=None):
51         super(DelimiterArgument, self).__init__(help, parsed_name, default)
52         self.caller_obj = caller_obj
53
54     @property 
55     def value(self):
56         if self.caller_obj.get_argument('recursive'):
57             return '/'
58         return getattr(self, '_value', self.default)
59     @value.setter 
60     def value(self, newvalue):
61         self._value = newvalue
62
63 class UntilArgument(ValueArgument):
64     def __init__(self, caller_obj, help='', parsed_name=None, default=None):
65         super(UntilArgument, self).__init__(help, parsed_name, default)
66         self.caller_obj = caller_obj
67
68     @property 
69     def value(self):
70         _value = getattr(self, '_value', self.default)
71         if _value is None:
72             return None
73         format = self.caller_obj.get_argument('format')
74         try:
75             t = strptime(_value, format)
76         except ValueError as err:
77             raise CLIError(message='in --until: '+unicode(err), importance=1)
78         return int(mktime(t))
79     @value.setter 
80     def value(self, newvalue):
81         self._value = newvalue
82
83 class MetaArgument(ValueArgument):
84     @property 
85     def value(self):
86         if self._value is None:
87             return self.default
88         metadict = dict()
89         for metastr in self._value.split('_'):
90             (key,val) = metastr.split(':')
91             metadict[key]=val
92         return metadict
93
94 class ProgressBarArgument(FlagArgument, IncrementalBar):
95
96     def __init__(self, help='', parsed_name='', default=True):
97         self.suffix = '%(percent)d%%'
98         super(ProgressBarArgument, self).__init__(help, parsed_name, default)
99
100     @property 
101     def value(self):
102         return getattr(self, '_value', self.default)
103     @value.setter 
104     def value(self, newvalue):
105         """By default, it is on (True)"""
106         self._value = not newvalue
107
108     def get_generator(self, message):
109         _message_len = 25
110         def progress_gen(n):
111             msg = message.ljust(_message_len)
112             for i in self.iter(range(n)):
113                 yield
114             yield
115         return progress_gen
116
117 class _pithos_init(object):
118     def __init__(self, arguments={}):
119         self.arguments = arguments
120         try:
121             self.config = self.get_argument('config')
122         except KeyError:
123             pass
124
125     def get_argument(self, arg_name):
126         return self.arguments[arg_name].value
127
128     def main(self):
129         self.token = self.config.get('store', 'token') or self.config.get('global', 'token')
130         self.base_url = self.config.get('store', 'url') or self.config.get('global', 'url')
131         self.account = self.config.get('store', 'account') or self.config.get('global', 'account')
132         self.container = self.config.get('store', 'container') or self.config.get('global',
133             'container')
134         self.client = PithosClient(base_url=self.base_url, token=self.token, account=self.account,
135             container=self.container)
136
137 class _store_account_command(_pithos_init):
138     """Base class for account level storage commands"""
139
140     def __init__(self, arguments={}):
141         super(_store_account_command, self).__init__(arguments)
142         self.arguments['account'] = ValueArgument('Specify the account', '--account')
143
144     def generator(self, message):
145        return None 
146
147     def main(self):
148         super(_store_account_command, self).main()
149         if self.arguments['account'].value is not None:
150             self.client.account = self.arguments['account'].value
151
152 class _store_container_command(_store_account_command):
153     """Base class for container level storage commands"""
154
155     def __init__(self, arguments={}):
156         super(_store_container_command, self).__init__(arguments)
157         self.arguments['container'] = ValueArgument('Specify the container name', '--container')
158         self.container = None
159         self.path = None
160
161     def extract_container_and_path(self, container_with_path, path_is_optional=True):
162         assert isinstance(container_with_path, str)
163         if ':' not in container_with_path:
164             if self.get_argument('container') is not None:
165                 self.container = self.get_argument('container')
166             else:
167                 self.container = self.client.container
168             if self.container is None:
169                 self.container = container_with_path
170             else:
171                 self.path = container_with_path
172             if not path_is_optional and self.path is None:
173                 raise CLIError(message="Object path is missing", status=11)
174             return
175         cnp = container_with_path.split(':')
176         self.container = cnp[0]
177         try:
178             self.path = cnp[1]
179         except IndexError:
180             if path_is_optional:
181                 self.path = None
182             else:
183                 raise CLIError(message="Object path is missing", status=11)
184
185     def main(self, container_with_path=None, path_is_optional=True):
186         super(_store_container_command, self).main()
187         if container_with_path is not None:
188             self.extract_container_and_path(container_with_path, path_is_optional)
189             self.client.container = self.container
190         elif self.get_argument('container') is not None:
191             self.client.container = self.get_argument('container')
192         self.container = self.client.container
193
194 """
195 @command()
196 class store_test(_store_container_command):
197     "Test stuff something""
198
199     def main(self):
200         super(self.__class__, self).main('pithos')
201         r = self.client.container_get()
202         print(unicode(r.content)+' '+unicode(r.json))
203 """
204
205 @command()
206 class store_list(_store_container_command):
207     """List containers, object trees or objects in a directory
208     """
209
210     def __init__(self, arguments = {}):
211         super(store_list, self).__init__(arguments)
212         self.arguments['detail'] = FlagArgument('show detailed output', '-l')
213         self.arguments['show_size'] = ValueArgument('print output in chunks of size N', '-N')
214         self.arguments['limit'] = ValueArgument('show limited output', '-n')
215         self.arguments['marker'] = ValueArgument('show output greater that marker', '--marker')
216         self.arguments['prefix'] = ValueArgument('show output staritng with prefix', '--prefix')
217         self.arguments['delimiter'] = ValueArgument('show output up to delimiter', '--delimiter')
218         self.arguments['path'] = ValueArgument('show output starting with prefix up to /', '--path')
219         self.arguments['meta'] = ValueArgument('show output haviung the specified meta keys',
220             '---meta', default=[])
221         self.arguments['if_modified_since'] = ValueArgument('show output modified since then',
222             '--if-modified-since')
223         self.arguments['if_unmodified_since'] = ValueArgument('show output not modified since then',
224             '--if-unmodified-since')
225         self.arguments['until'] = UntilArgument(self, 'show metadata until then', '--until')
226         self.arguments['format'] = ValueArgument('format to parse until data (default: d/m/Y H:M:S',
227             '--format')
228         self.arguments['shared'] = FlagArgument('show only shared', '--shared')
229         self.arguments['public'] = FlagArgument('show only public', '--public')
230
231     def print_objects(self, object_list):
232         import sys
233         try:
234             limit = self.get_argument('show_size')
235             limit = int(limit)
236         except AttributeError:
237             pass
238         #index = 0
239         for index,obj in enumerate(object_list):
240             if not obj.has_key('content_type'):
241                 continue
242             pretty_obj = obj.copy()
243             index += 1
244             empty_space = ' '*(len(str(len(object_list))) - len(str(index)))
245             if obj['content_type'] == 'application/directory':
246                 isDir = True
247                 size = 'D'
248             else:
249                 isDir = False
250                 size = format_size(obj['bytes'])
251                 pretty_obj['bytes'] = '%s (%s)'%(obj['bytes'],size)
252             oname = bold(obj['name'])
253             if self.get_argument('detail'):
254                 print('%s%s. %s'%(empty_space, index, oname))
255                 print_dict(pretty_keys(pretty_obj), exclude=('name'))
256                 print
257             else:
258                 oname = '%s%s. %6s %s'%(empty_space, index, size, oname)
259                 oname += '/' if isDir else ''
260                 print(oname)
261             if limit <= index < len(object_list) and index%limit == 0:
262                 print('(press "enter" to continue)')
263                 sys.stdin.read(1)
264
265     def print_containers(self, container_list):
266         import sys
267         try:
268             limit = self.get_argument('show_size')
269             limit = int(limit)
270         except AttributeError:
271             pass
272         for index,container in enumerate(container_list):
273             if container.has_key('bytes'):
274                 size = format_size(container['bytes']) 
275             cname = '%s. %s'%(index+1, bold(container['name']))
276             if self.get_argument('detail'):
277                 print(cname)
278                 pretty_c = container.copy()
279                 if container.has_key('bytes'):
280                     pretty_c['bytes'] = '%s (%s)'%(container['bytes'], size)
281                 print_dict(pretty_keys(pretty_c), exclude=('name'))
282                 print
283             else:
284                 if container.has_key('count') and container.has_key('bytes'):
285                     print('%s (%s, %s objects)' % (cname, size, container['count']))
286                 else:
287                     print(cname)
288             if limit <= index < len(container_list) and index%limit == 0:
289                 print('(press "enter" to continue)')
290                 sys.stdin.read(1)
291
292     def main(self, container____path__=None):
293         super(self.__class__, self).main(container____path__)
294         try:
295             if self.container is None:
296                 r = self.client.account_get(limit=self.get_argument('limit'),
297                     marker=self.get_argument('marker'),
298                     if_modified_since=self.get_argument('if_modified_since'),
299                     if_unmodified_since=self.get_argument('if_unmodified_since'),
300                     until=self.get_argument('until'),
301                     show_only_shared=self.get_argument('shared'))
302                 self.print_containers(r.json)
303             else:
304                 r = self.client.container_get(limit=self.get_argument('limit'),
305                     marker=self.get_argument('marker'), prefix=self.get_argument('prefix'),
306                     delimiter=self.get_argument('delimiter'), path=self.get_argument('path'),
307                     if_modified_since=self.get_argument('if_modified_since'),
308                     if_unmodified_since=self.get_argument('if_unmodified_since'),
309                     until=self.get_argument('until'),
310                     meta=self.get_argument('meta'), show_only_shared=self.get_argument('shared'))
311                 self.print_objects(r.json)
312         except ClientError as err:
313             raiseCLIError(err)
314
315 @command()
316 class store_mkdir(_store_container_command):
317     """Create a directory"""
318
319     def main(self, container___directory):
320         super(self.__class__, self).main(container___directory, path_is_optional=False)
321         try:
322             self.client.create_directory(self.path)
323         except ClientError as err:
324             raiseCLIError(err)
325
326 @command()
327 class store_create(_store_container_command):
328     """Create a container or a directory object"""
329
330
331     def __init__(self, arguments={}):
332         super(self.__class__, self).__init__(arguments)
333         self.arguments['versioning'] = ValueArgument('set container versioning (auto/none)',
334             '--versioning')
335         self.arguments['quota'] = ValueArgument('set default container quota', '--quota')
336         self.arguments['meta'] = MetaArgument('set container metadata', '---meta')
337
338     def main(self, container____directory__):
339         super(self.__class__, self).main(container____directory__)
340         try:
341             if self.path is None:
342                 self.client.container_put(quota=self.get_argument('quota'),
343                     versioning=self.get_argument('versioning'),
344                     metadata=self.get_argument('metadata'))
345             else:
346                 self.client.create_directory(self.path)
347         except ClientError as err:
348             raiseCLIError(err)
349
350 @command()
351 class store_copy(_store_container_command):
352     """Copy an object"""
353
354     def __init__(self, arguments={}):
355         super(self.__class__, self).__init__(arguments)
356         self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
357         self.arguments['public']=ValueArgument('make object publicly accessible', '--public')
358         self.arguments['content_type']=ValueArgument('change object\'s content type',
359             '--content-type')
360         self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
361             help = u'mass copy objects with path staring with src_object + delimiter')
362         self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
363
364     def main(self, source_container___path, destination_container____path__):
365         super(self.__class__, self).main(source_container___path, path_is_optional=False)
366         try:
367             dst = destination_container____path__.split(':')
368             dst_cont = dst[0]
369             dst_path = dst[1] if len(dst) > 1 else False
370             self.client.copy_object(src_container = self.container, src_object = self.path,
371                 dst_container = dst_cont, dst_object = dst_path,
372                 source_version=self.get_argument('source_version'),
373                 public=self.get_argument('public'),
374                 content_type=self.get_argument('content_type'),
375                 delimiter=self.get_argument('delimiter'))
376         except ClientError as err:
377             raiseCLIError(err)
378
379 @command()
380 class store_move(_store_container_command):
381     """Copy an object"""
382
383     def __init__(self, arguments={}):
384         super(self.__class__, self).__init(arguments)
385
386         self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
387         self.arguments['public']=ValueArgument('make object publicly accessible', '--public')
388         self.arguments['content_type']=ValueArgument('change object\'s content type',
389             '--content-type')
390         self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
391             help = u'mass copy objects with path staring with src_object + delimiter')
392         self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
393
394     def main(self, source_container___path, destination_container____path__):
395         super(self.__class__, self).main(source_container___path, path_is_optional=False)
396         try:
397             dst = destination_container____path__.split(':')
398             dst_cont = dst[0]
399             dst_path = dst[1] if len(dst) > 1 else False
400             self.client.move_object(src_container = self.container, src_object = self.path,
401                 dst_container = dst_cont, dst_object = dst_path,
402                 source_version=self.get_argument('source_version'),
403                 public=self.get_argument('public'),
404                 content_type=self.get_argument('content_type'),
405                 delimiter=self.get_argument('delimiter'))
406         except ClientError as err:
407             raiseCLIError(err)
408
409 @command()
410 class store_append(_store_container_command):
411     """Append local file to (existing) remote object"""
412
413     def __init__(self, arguments={}):
414         super(self.__class__, self).__init__(arguments)
415         self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
416             '--no-progress-bar')
417
418     def main(self, local_path, container___path):
419         super(self.__class__, self).main(container___path, path_is_optional=False)
420         try:
421             f = open(local_path, 'r')
422             upload_cb = self.arguments['progress_bar'].get_generator('Appending blocks')
423             self.client.append_object(object=self.path, source_file = f, upload_cb = upload_cb)
424         except ClientError as err:
425             raiseCLIError(err)
426
427 @command()
428 class store_truncate(_store_container_command):
429     """Truncate remote file up to a size"""
430
431     def main(self, container___path, size=0):
432         super(self.__class__, self).main(container___path, path_is_optional=False)
433         try:
434             self.client.truncate_object(self.path, size)
435         except ClientError as err:
436             raiseCLIError(err)
437
438 @command()
439 class store_overwrite(_store_container_command):
440     """Overwrite part (from start to end) of a remote file"""
441
442     def __init__(self, arguments={}):
443         super(self.__class__, self).__init__(arguments)
444         self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
445             '--no-progress-bar')
446
447     def main(self, local_path, container___path, start, end):
448         super(self.__class__, self).main(container___path, path_is_optional=False)
449         try:
450             f = open(local_path, 'r')
451             upload_cb = self.arguments['progress_bar'].get_generator('Overwritting blocks')
452             self.client.overwrite_object(object=self.path, start=start, end=end,
453                 source_file=f, upload_cb = upload_cb)
454         except ClientError as err:
455             raiseCLIError(err)
456
457 class SharingArgument(ValueArgument):
458     @property 
459     def value(self):
460         return getattr(self, '_value', self.default)
461     @value.setter
462     def value(self, newvalue):
463         perms = {}
464         try:
465             permlist = newvalue.split(' ')
466         except AttributeError:
467             return
468         for p in permlist:
469             try:
470                 (key,val) = p.split('=')
471             except ValueError:
472                 raise CLIError(message='Error in --sharing', details='Incorrect format', importance=1)
473             if key.lower() not in ('read', 'write'):
474                 raise CLIError(message='Error in --sharing', details='Invalid permition key %s'%key, importance=1)
475             val_list = val.split(',')
476             if not perms.has_key(key):
477                 perms[key]=[]
478             for item in val_list:
479                 if item not in perms[key]:
480                     perms[key].append(item)
481         self._value = perms
482
483 @command()
484 class store_manifest(_store_container_command):
485     """Create a remote file with uploaded parts by manifestation"""
486
487     def __init__(self, arguments={}):
488         super(self.__class__, self).__init__(arguments)
489         self.arguments['etag'] = ValueArgument('check written data', '--etag')
490         self.arguments['content_encoding']=ValueArgument('provide the object MIME content type',
491             '--content-encoding')
492         self.arguments['content_disposition'] = ValueArgument('provide the presentation style of the object',
493             '--content-disposition')
494         self.arguments['content_type']=ValueArgument('create object with specific content type',
495             '--content-type')
496         self.arguments['sharing']=SharingArgument(parsed_name='--sharing',
497             help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
498         self.arguments['public']=ValueArgument('make object publicly accessible', '--public')
499         
500     def main(self, container___path):
501         super(self.__class__, self).main(container___path, path_is_optional=False)
502         try:
503             self.client.create_object_by_manifestation(self.path,
504                 content_encoding=self.get_argument('content_encoding'),
505                 content_disposition=self.get_argument('content_disposition'),
506                 content_type=self.get_argument('content_type'),
507                 sharing=self.get_argument('sharing'), public=self.get_argument('public'))
508         except ClientError as err:
509             raiseCLIError(err)
510
511 @command()
512 class store_upload(_store_container_command):
513     """Upload a file"""
514
515     def update_parser(self, parser):
516         super(self.__class__, self).update_parser(parser)
517         parser.add_argument('--use_hashes', action='store_true', dest='use_hashes', default=False,
518             help='provide hashmap file instead of data')
519         parser.add_argument('--unchunked', action='store_true', dest='unchunked', default=False,
520             help='avoid chunked transfer mode')
521         parser.add_argument('--etag', action='store', dest='etag', default=None,
522             help='check written data')
523         parser.add_argument('--content-encoding', action='store', dest='content_encoding',
524             default=None, help='provide the object MIME content type')
525         parser.add_argument('--content-disposition', action='store', dest='content_disposition',
526             default=None, help='provide the presentation style of the object')
527         parser.add_argument('--content-type', action='store', dest='content_type', default=None,
528             help='create object with specific content type')
529         parser.add_argument('--sharing', action='store', dest='sharing', default=None,
530             help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
531         parser.add_argument('--public', action='store_true', dest='public', default=False,
532             help='make object publicly accessible')
533         parser.add_argument('--with-pool-size', action='store', dest='poolsize', default=None,
534             help='Set the greenlet pool size (advanced)')
535
536     def getsharing(self, orelse={}):
537         permstr = getattr(self.args, 'sharing')
538         if permstr is None:
539             return orelse
540         perms = {}
541         for p in permstr.split(' '):
542             (key, val) = p.split('=')
543             if key.lower() not in ('read', 'write'):
544                 raise CLIError(message='in --sharing: Invalid permition key', importance=1)
545             val_list = val.split(',')
546             if not perms.has_key(key):
547                 perms[key]=[]
548             for item in val_list:
549                 if item not in perms[key]:
550                     perms[key].append(item)
551         return perms
552
553     def main(self, local_path, container____path__):
554         super(self.__class__, self).main(container____path__)
555         remote_path = local_path if self.path is None else self.path
556         poolsize = getattr(self.args, 'poolsize')
557         if poolsize is not None:
558             self.POOL_SIZE = int(poolsize)
559         try:
560             with open(local_path) as f:
561                 if getattr(self.args, 'unchunked'):
562                     self.client.upload_object_unchunked(remote_path, f,
563                     etag=getattr(self.args, 'etag'), withHashFile=getattr(self.args, 'use_hashes'),
564                     content_encoding=getattr(self.args, 'content_encoding'),
565                     content_disposition=getattr(self.args, 'content_disposition'),
566                     content_type=getattr(self.args, 'content_type'), sharing=self.getsharing(),
567                     public=getattr(self.args, 'public'))
568                 else:
569                     hash_cb = self.progress('Calculating block hashes')
570                     upload_cb = self.progress('Uploading blocks')
571                     self.client.upload_object(remote_path, f, hash_cb=hash_cb, upload_cb=upload_cb,
572                     content_encoding=getattr(self.args, 'content_encoding'),
573                     content_disposition=getattr(self.args, 'content_disposition'),
574                     content_type=getattr(self.args, 'content_type'), sharing=self.getsharing(),
575                     public=getattr(self.args, 'public'))
576         except ClientError as err:
577             raiseCLIError(err)
578         print 'Upload completed'
579
580 @command()
581 class store_download(_store_container_command):
582     """Download a file"""
583
584     def update_parser(self, parser):
585         super(self.__class__, self).update_parser(parser)
586         parser.add_argument('--no-progress-bar', action='store_true', dest='no_progress_bar',
587             default=False, help='Dont display progress bars')
588         parser.add_argument('--resume', action='store_true', dest='resume', default=False,
589             help='Resume a previous download instead of overwritting it')
590         parser.add_argument('--range', action='store', dest='range', default=None,
591             help='show range of data')
592         parser.add_argument('--if-match', action='store', dest='if_match', default=None,
593             help='show output if ETags match')
594         parser.add_argument('--if-none-match', action='store', dest='if_none_match', default=None,
595             help='show output if ETags don\'t match')
596         parser.add_argument('--if-modified-since', action='store', dest='if_modified_since',
597             default=None, help='show output if modified since then')
598         parser.add_argument('--if-unmodified-since', action='store', dest='if_unmodified_since',
599             default=None, help='show output if not modified since then')
600         parser.add_argument('--object-version', action='store', dest='object_version', default=None,
601             help='get the specific version')
602         parser.add_argument('--with-pool-size', action='store', dest='poolsize', default=None,
603             help='Set the greenlet pool size (advanced)')
604
605     def main(self, container___path, local_path=None):
606         super(self.__class__, self).main(container___path, path_is_optional=False)
607
608         #setup output stream
609         parallel = False
610         if local_path is None:
611             out = stdout
612         else:
613             try:
614                 if hasattr(self.args, 'resume') and getattr(self.args, 'resume'):
615                     out=open(local_path, 'rwb+')
616                 else:
617                     out=open(local_path, 'wb+')
618             except IOError as err:
619                 raise CLIError(message='Cannot write to file %s - %s'%(local_path,unicode(err)),
620                     importance=1)
621         download_cb = None if getattr(self.args, 'no_progress_bar') \
622         else self.progress('Downloading')
623         poolsize = getattr(self.args, 'poolsize')
624         if poolsize is not None:
625             self.POOL_SIZE = int(poolsize)
626
627         try:
628             self.client.download_object(self.path, out, download_cb,
629                 range=getattr(self.args, 'range'), version=getattr(self.args,'object_version'),
630                 if_match=getattr(self.args, 'if_match'), resume=getattr(self.args, 'resume'),
631                 if_none_match=getattr(self.args, 'if_none_match'),
632                 if_modified_since=getattr(self.args, 'if_modified_since'),
633                 if_unmodified_since=getattr(self.args, 'if_unmodified_since'))
634         except ClientError as err:
635             raiseCLIError(err)
636         except KeyboardInterrupt:
637             print('\ndownload canceled by user')
638             if local_path is not None:
639                 print('to resume, re-run with --resume')
640         print
641
642 @command()
643 class store_hashmap(_store_container_command):
644     """Get the hashmap of an object"""
645
646     def update_parser(self, parser):
647         super(self.__class__, self).update_parser(parser)
648         parser.add_argument('--if-match', action='store', dest='if_match', default=None,
649             help='show output if ETags match')
650         parser.add_argument('--if-none-match', action='store', dest='if_none_match', default=None,
651             help='show output if ETags dont match')
652         parser.add_argument('--if-modified-since', action='store', dest='if_modified_since',
653             default=None, help='show output if modified since then')
654         parser.add_argument('--if-unmodified-since', action='store', dest='if_unmodified_since',
655             default=None, help='show output if not modified since then')
656         parser.add_argument('--object-version', action='store', dest='object_version', default=None,
657             help='get the specific version')
658
659     def main(self, container___path):
660         super(self.__class__, self).main(container___path, path_is_optional=False)
661         try:
662             data = self.client.get_object_hashmap(self.path,
663                 version=getattr(self.args, 'object_version'),
664                 if_match=getattr(self.args, 'if_match'),
665                 if_none_match=getattr(self.args, 'if_none_match'),
666                 if_modified_since=getattr(self.args, 'if_modified_since'),
667                 if_unmodified_since=getattr(self.args, 'if_unmodified_since'))
668         except ClientError as err:
669             raiseCLIError(err)
670         print_dict(data)
671
672 @command()
673 class store_delete(_store_container_command):
674     """Delete a container [or an object]"""
675
676     def update_parser(self, parser):
677         super(self.__class__, self).update_parser(parser)
678         parser.add_argument('--until', action='store', dest='until', default=None,
679             help='remove history until that date')
680         parser.add_argument('--format', action='store', dest='format', default='%d/%m/%Y %H:%M:%S',
681             help='format to parse until date (default: d/m/Y H:M:S)')
682         parser.add_argument('--delimiter', action='store', dest='delimiter',
683             default=None, 
684             help='mass delete objects with path staring with <object><delimiter>')
685         parser.add_argument('-r', action='store_true', dest='recursive', default=False,
686             help='empty dir or container and delete (if dir)')
687     
688     def getuntil(self, orelse=None):
689         if hasattr(self.args, 'until'):
690             import time
691             until = getattr(self.args, 'until')
692             if until is None:
693                 return None
694             format = getattr(self.args, 'format')
695             try:
696                 t = time.strptime(until, format)
697             except ValueError as err:
698                 raise CLIError(message='in --until: '+unicode(err), importance=1)
699             return int(time.mktime(t))
700         return orelse
701
702     def getdelimiter(self, orelse=None):
703         try:
704             dlm = getattr(self.args, 'delimiter')
705             if dlm is None:
706                 return '/' if getattr(self.args, 'recursive') else orelse
707         except AttributeError:
708             return orelse
709         return dlm
710
711     def main(self, container____path__):
712         super(self.__class__, self).main(container____path__)
713         try:
714             if self.path is None:
715                 self.client.del_container(until=self.getuntil(), delimiter=self.getdelimiter())
716             else:
717                 #self.client.delete_object(self.path)
718                 self.client.del_object(self.path, until=self.getuntil(),
719                     delimiter=self.getdelimiter())
720         except ClientError as err:
721             raiseCLIError(err)
722
723 @command()
724 class store_purge(_store_container_command):
725     """Purge a container"""
726     
727     def main(self, container):
728         super(self.__class__, self).main(container)
729         try:
730             self.client.purge_container()
731         except ClientError as err:
732             raiseCLIError(err)
733
734 @command()
735 class store_publish(_store_container_command):
736     """Publish an object"""
737
738     def main(self, container___path):
739         super(self.__class__, self).main(container___path, path_is_optional=False)
740         try:
741             self.client.publish_object(self.path)
742         except ClientError as err:
743             raiseCLIError(err)
744
745 @command()
746 class store_unpublish(_store_container_command):
747     """Unpublish an object"""
748
749     def main(self, container___path):
750         super(self.__class__, self).main(container___path, path_is_optional=False)
751         try:
752             self.client.unpublish_object(self.path)
753         except ClientError as err:
754             raiseCLIError(err)
755
756 @command()
757 class store_permitions(_store_container_command):
758     """Get object read/write permitions"""
759
760     def main(self, container___path):
761         super(self.__class__, self).main(container___path, path_is_optional=False)
762         try:
763             reply = self.client.get_object_sharing(self.path)
764             print_dict(reply)
765         except ClientError as err:
766             raiseCLIError(err)
767
768 @command()
769 class store_setpermitions(_store_container_command):
770     """Set sharing permitions"""
771
772     def format_permition_dict(self,permitions):
773         read = False
774         write = False
775         for perms in permitions:
776             splstr = perms.split('=')
777             if 'read' == splstr[0]:
778                 read = [user_or_group.strip() \
779                 for user_or_group in splstr[1].split(',')]
780             elif 'write' == splstr[0]:
781                 write = [user_or_group.strip() \
782                 for user_or_group in splstr[1].split(',')]
783             else:
784                 read = False
785                 write = False
786         if not read and not write:
787             raise CLIError(message='Usage:\tread=<groups,users> write=<groups,users>',
788                 importance=0)
789         return (read,write)
790
791     def main(self, container___path, *permitions):
792         super(self.__class__, self).main(container___path, path_is_optional=False)
793         (read, write) = self.format_permition_dict(permitions)
794         try:
795             self.client.set_object_sharing(self.path,
796                 read_permition=read, write_permition=write)
797         except ClientError as err:
798             raiseCLIError(err)
799
800 @command()
801 class store_delpermitions(_store_container_command):
802     """Delete all sharing permitions"""
803
804     def main(self, container___path):
805         super(self.__class__, self).main(container___path, path_is_optional=False)
806         try:
807             self.client.del_object_sharing(self.path)
808         except ClientError as err:
809             raiseCLIError(err)
810
811 @command()
812 class store_info(_store_container_command):
813     """Get information for account [, container [or object]]"""
814
815     
816     def main(self, container____path__=None):
817         super(self.__class__, self).main(container____path__)
818         try:
819             if self.container is None:
820                 reply = self.client.get_account_info()
821             elif self.path is None:
822                 reply = self.client.get_container_info(self.container)
823             else:
824                 reply = self.client.get_object_info(self.path)
825         except ClientError as err:
826             raiseCLIError(err)
827         print_dict(reply)
828
829 @command()
830 class store_meta(_store_container_command):
831     """Get custom meta-content for account [, container [or object]]"""
832
833     def update_parser(self, parser):
834         super(self.__class__, self).update_parser(parser)
835         parser.add_argument('-l', action='store_true', dest='detail', default=False,
836             help='show detailed output')
837         parser.add_argument('--until', action='store', dest='until', default=None,
838             help='show metadata until that date')
839         dateformat='%d/%m/%Y %H:%M:%S'
840         parser.add_argument('--format', action='store', dest='format', default=dateformat,
841             help='format to parse until date (default: "d/m/Y H:M:S")')
842         parser.add_argument('--object_version', action='store', dest='object_version', default=None,
843             help='show specific version \ (applies only for objects)')
844
845     def getuntil(self, orelse=None):
846         if hasattr(self.args, 'until'):
847             import time
848             until = getattr(self.args, 'until')
849             if until is None:
850                 return None
851             format = getattr(self.args, 'format')
852             #except TypeError:
853             try:
854                 t = time.strptime(until, format)
855             except ValueError as err:
856                 raise CLIError(message='in --until: '+unicode(err), importance=1)
857             return int(time.mktime(t))
858         return orelse
859
860     def main(self, container____path__ = None):
861         super(self.__class__, self).main(container____path__)
862
863         detail = getattr(self.args, 'detail')
864         try:
865             if self.container is None:
866                 print(bold(self.client.account))
867                 if detail:
868                     reply = self.client.get_account_info(until=self.getuntil())
869                 else:
870                     reply = self.client.get_account_meta(until=self.getuntil())
871                     reply = pretty_keys(reply, '-')
872             elif self.path is None:
873                 print(bold(self.client.account+': '+self.container))
874                 if detail:
875                     reply = self.client.get_container_info(until = self.getuntil())
876                 else:
877                     cmeta = self.client.get_container_meta(until=self.getuntil())
878                     ometa = self.client.get_container_object_meta(until=self.getuntil())
879                     reply = {'container-meta':pretty_keys(cmeta, '-'),
880                         'object-meta':pretty_keys(ometa, '-')}
881             else:
882                 print(bold(self.client.account+': '+self.container+':'+self.path))
883                 version=getattr(self.args, 'object_version')
884                 if detail:
885                     reply = self.client.get_object_info(self.path, version = version)
886                 else:
887                     reply = self.client.get_object_meta(self.path, version=version)
888                     reply = pretty_keys(pretty_keys(reply, '-'))
889         except ClientError as err:
890             raiseCLIError(err)
891         print_dict(reply)
892
893 @command()
894 class store_setmeta(_store_container_command):
895     """Set a new metadatum for account [, container [or object]]"""
896
897     def main(self, metakey___metaval, container____path__=None):
898         super(self.__class__, self).main(container____path__)
899         try:
900             metakey, metavalue = metakey___metaval.split(':')
901         except ValueError:
902             raise CLIError(message='Meta variables should be formated as metakey:metavalue',
903                 importance=1)
904         try:
905             if self.container is None:
906                 self.client.set_account_meta({metakey:metavalue})
907             elif self.path is None:
908                 self.client.set_container_meta({metakey:metavalue})
909             else:
910                 self.client.set_object_meta(self.path, {metakey:metavalue})
911         except ClientError as err:
912             raiseCLIError(err)
913
914 @command()
915 class store_delmeta(_store_container_command):
916     """Delete an existing metadatum of account [, container [or object]]"""
917
918     def main(self, metakey, container____path__=None):
919         super(self.__class__, self).main(container____path__)
920         try:
921             if self.container is None:
922                 self.client.del_account_meta(metakey)
923             elif self.path is None:
924                 self.client.del_container_meta(metakey)
925             else:
926                 self.client.del_object_meta(metakey, self.path)
927         except ClientError as err:
928             raiseCLIError(err)
929
930 @command()
931 class store_quota(_store_account_command):
932     """Get  quota for account [or container]"""
933
934     def main(self, container = None):
935         super(self.__class__, self).main()
936         try:
937             if container is None:
938                 reply = self.client.get_account_quota()
939             else:
940                 reply = self.client.get_container_quota(container)
941         except ClientError as err:
942             raiseCLIError(err)
943         print_dict(reply)
944
945 @command()
946 class store_setquota(_store_account_command):
947     """Set new quota (in KB) for account [or container]"""
948
949     def main(self, quota, container = None):
950         super(self.__class__, self).main()
951         try:
952             if container is None:
953                 self.client.set_account_quota(quota)
954             else:
955                 self.client.container = container
956                 self.client.set_container_quota(quota)
957         except ClientError as err:
958             raiseCLIError(err)
959
960 @command()
961 class store_versioning(_store_account_command):
962     """Get  versioning for account [or container ]"""
963
964     def main(self, container = None):
965         super(self.__class__, self).main()
966         try:
967             if container is None:
968                 reply = self.client.get_account_versioning()
969             else:
970                 reply = self.client.get_container_versioning(container)
971         except ClientError as err:
972             raiseCLIError(err)
973         print_dict(reply)
974
975 @command()
976 class store_setversioning(_store_account_command):
977     """Set new versioning (auto, none) for account [or container]"""
978
979     def main(self, versioning, container = None):
980         super(self.__class__, self).main()
981         try:
982             if container is None:
983                 self.client.set_account_versioning(versioning)
984             else:
985                 self.client.container = container
986                 self.client.set_container_versioning(versioning)
987         except ClientError as err:
988             raiseCLIError(err)
989
990 @command()
991 class store_group(_store_account_command):
992     """Get user groups details for account"""
993
994     def main(self):
995         super(self.__class__, self).main()
996         try:
997             reply = self.client.get_account_group()
998         except ClientError as err:
999             raiseCLIError(err)
1000         print_dict(reply)
1001
1002 @command()
1003 class store_setgroup(_store_account_command):
1004     """Create/update a new user group on account"""
1005
1006     def main(self, groupname, *users):
1007         super(self.__class__, self).main()
1008         try:
1009             self.client.set_account_group(groupname, users)
1010         except ClientError as err:
1011             raiseCLIError(err)
1012
1013 @command()
1014 class store_delgroup(_store_account_command):
1015     """Delete a user group on an account"""
1016
1017     def main(self, groupname):
1018         super(self.__class__, self).main()
1019         try:
1020             self.client.del_account_group(groupname)
1021         except ClientError as err:
1022             raiseCLIError(err)
1023
1024 @command()
1025 class store_sharers(_store_account_command):
1026     """List the accounts that share objects with default account"""
1027
1028     def update_parser(self, parser):
1029         super(store_sharers, self).update_parser(parser)
1030         parser.add_argument('-l', action='store_true', dest='detail', default=False,
1031             help='show detailed output')
1032         parser.add_argument('-n', action='store',  dest='limit', default=10000,
1033             help='show limited output')
1034         parser.add_argument('--marker', action='store', dest='marker', default=None,
1035             help='show output greater then marker')
1036
1037     def main(self):
1038         super(self.__class__, self).main()
1039         try:
1040             accounts = self.client.get_sharing_accounts(marker=getattr(self.args, 'marker'))
1041         except ClientError as err:
1042             raiseCLIError(err)
1043
1044         for acc in accounts:
1045             stdout.write(bold(acc['name'])+' ')
1046             if getattr(self.args, 'detail'):
1047                 print_dict(acc, exclude='name', ident=18)
1048         if not getattr(self.args, 'detail'):
1049             print
1050
1051 @command()
1052 class store_versions(_store_container_command):
1053     """Get the version list of an object"""
1054
1055     def main(self, container___path):
1056         super(store_versions, self).main(container___path)
1057         try:
1058             versions = self.client.get_object_versionlist(self.path)
1059         except ClientError as err:
1060             raise CLIError(err)
1061
1062         print('%s:%s versions'%(self.container,self.path))
1063         for vitem in versions:
1064             t = localtime(float(vitem[1]))
1065             vid = bold(unicode(vitem[0]))
1066             print('\t%s \t(%s)'%(vid, strftime('%d-%m-%Y %H:%M:%S', t)))