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