Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ cd6ada1d

History | View | Annotate | Download (31.4 kB)

1
# Copyright 2011 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.
33

    
34
from httplib import HTTPConnection, HTTP
35
from sys import stdin
36
from xml.dom import minidom
37

    
38
import json
39
import types
40
import socket
41
import pithos.api.faults
42

    
43
ERROR_CODES = {304:'Not Modified',
44
               400:'Bad Request',
45
               401:'Unauthorized',
46
               404:'Not Found',
47
               409:'Conflict',
48
               411:'Length Required',
49
               412:'Precondition Failed',
50
               416:'Range Not Satisfiable',
51
               422:'Unprocessable Entity',
52
               503:'Service Unavailable'}
53

    
54
class Fault(Exception):
55
    def __init__(self, data='', status=None):
56
        if data == '' and status in ERROR_CODES.keys():
57
            data = ERROR_CODES[status]
58
        Exception.__init__(self, data)
59
        self.data = data
60
        self.status = status
61

    
62
class Client(object):
63
    def __init__(self, host, token, account, api='v1', verbose=False, debug=False):
64
        """`host` can also include a port, e.g '127.0.0.1:8000'."""
65
        
66
        self.host = host
67
        self.account = account
68
        self.api = api
69
        self.verbose = verbose or debug
70
        self.debug = debug
71
        self.token = token
72
    
73
    def _req(self, method, path, body=None, headers={}, format='text',
74
            params={}):
75
        full_path = '/%s/%s%s?format=%s' % (self.api, self.account, path,
76
                                            format)
77
        for k,v in params.items():
78
            if v:
79
                full_path = '%s&%s=%s' %(full_path, k, v)
80
            else:
81
                full_path = '%s&%s' %(full_path, k)
82
        conn = HTTPConnection(self.host)
83
        
84
        #encode whitespace
85
        full_path = full_path.replace(' ', '%20')
86
        
87
        kwargs = {}
88
        for k,v in headers.items():
89
            headers.pop(k)
90
            k = k.replace('_', '-')
91
            headers[k] = v
92
        
93
        kwargs['headers'] = headers
94
        kwargs['headers']['X-Auth-Token'] = self.token
95
        if body:
96
            kwargs['body'] = body
97
            kwargs['headers'].setdefault('content-type',
98
                                         'application/octet-stream')
99
        kwargs['headers'].setdefault('content-length', len(body) if body else 0)
100
        try:
101
            #print '*', method, full_path, kwargs
102
            conn.request(method, full_path, **kwargs)
103
        except socket.error, e:
104
            raise Fault(status=503)
105
            
106
        resp = conn.getresponse()
107
        headers = dict(resp.getheaders())
108
        
109
        if self.verbose:
110
            print '%d %s' % (resp.status, resp.reason)
111
            for key, val in headers.items():
112
                print '%s: %s' % (key.capitalize(), val)
113
            print
114
        
115
        length = resp.getheader('content-length', None)
116
        data = resp.read(length)
117
        if self.debug:
118
            print data
119
            print
120
        
121
        if int(resp.status) in ERROR_CODES.keys():
122
            #print '**', resp.status
123
            raise Fault(data, int(resp.status))
124
        
125
        #print '**',  resp.status, headers, data
126
        return resp.status, headers, data
127
    
128
    def delete(self, path, format='text', params={}):
129
        return self._req('DELETE', path, format=format, params=params)
130
    
131
    def get(self, path, format='text', headers=None, params={}):
132
        return self._req('GET', path, headers=headers, format=format,
133
                        params=params)
134
    
135
    def head(self, path, format='text', params={}):
136
        return self._req('HEAD', path, format=format, params=params)
137
    
138
    def post(self, path, body=None, format='text', headers=None, params={}):
139
        return self._req('POST', path, body, headers=headers, format=format,
140
                        params=params)
141
    
142
    def put(self, path, body=None, format='text', headers=None):
143
        return self._req('PUT', path, body, headers=headers, format=format)
144
    
145
    def _list(self, path, format='text', params={}, **headers):
146
        status, headers, data = self.get(path, format=format, headers=headers,
147
                                         params=params)
148
        if format == 'json':
149
            data = json.loads(data) if data else ''
150
        elif format == 'xml':
151
            data = minidom.parseString(data)
152
        else:
153
            data = data.strip().split('\n') if data else ''
154
        return data
155
    
156
    def _get_metadata(self, path, prefix=None, params={}):
157
        status, headers, data = self.head(path, params=params)
158
        prefixlen = len(prefix) if prefix else 0
159
        meta = {}
160
        for key, val in headers.items():
161
            if prefix and not key.startswith(prefix):
162
                continue
163
            elif prefix and key.startswith(prefix):
164
                key = key[prefixlen:]
165
            meta[key] = val
166
        return meta
167
    
168
    def _filter(self, l, d):
169
        """
170
        filter out from l elements having the metadata values provided
171
        """
172
        ll = l
173
        for elem in l:
174
            if type(elem) == types.DictionaryType:
175
                for key in d.keys():
176
                    k = 'x_object_meta_%s' % key
177
                    if k in elem.keys() and elem[k] == d[key]:
178
                        ll.remove(elem)
179
                        break
180
        return ll
181
    
182
class OOS_Client(Client):
183
    """Openstack Object Storage Client"""
184
    
185
    def _update_metadata(self, path, entity, **meta):
186
        """adds new and updates the values of previously set metadata"""
187
        ex_meta = self.retrieve_account_metadata(restricted=True)
188
        ex_meta.update(meta)
189
        headers = {}
190
        prefix = 'x-%s-meta-' % entity
191
        for k,v in ex_meta.items():
192
            k = '%s%s' % (prefix, k)
193
            headers[k] = v
194
        return self.post(path, headers=headers)
195
    
196
    def _reset_metadata(self, path, entity, **meta):
197
        """
198
        overwrites all user defined metadata
199
        """
200
        headers = {}
201
        prefix = 'x-%s-meta-' % entity
202
        for k,v in meta.items():
203
            k = '%s%s' % (prefix, k)
204
            headers[k] = v
205
        return self.post(path, headers=headers)
206
    
207
    def _delete_metadata(self, path, entity, meta=[]):
208
        """delete previously set metadata"""
209
        ex_meta = self.retrieve_account_metadata(restricted=True)
210
        headers = {}
211
        prefix = 'x-%s-meta-' % entity
212
        for k in ex_meta.keys():
213
            if k in meta:
214
                headers['%s%s' % (prefix, k)] = ex_meta[k]
215
        return self.post(path, headers=headers)
216
    
217
    # Storage Account Services
218
    
219
    def list_containers(self, format='text', limit=10000, marker=None, params={},
220
                        **headers):
221
        """lists containers"""
222
        if not params:
223
            params = {}
224
        params.update({'limit':limit, 'marker':marker})
225
        return self._list('', format, params, **headers)
226
    
227
    def retrieve_account_metadata(self, restricted=False, **params):
228
        """returns the account metadata"""
229
        prefix = 'x-account-meta-' if restricted else None
230
        return self._get_metadata('', prefix, params)
231
    
232
    def update_account_metadata(self, **meta):
233
        """updates the account metadata"""
234
        return self._update_metadata('', 'account', **meta)
235
        
236
    def delete_account_metadata(self, meta=[]):
237
        """deletes the account metadata"""
238
        return self._delete_metadata('', 'account', meta)
239
    
240
    def reset_account_metadata(self, **meta):
241
        """resets account metadata"""
242
        return self._reset_metadata('', 'account', **meta)
243
    
244
    # Storage Container Services
245
    
246
    def _filter_trashed(self, l):
247
        return self._filter(l, {'trash':'true'})
248
    
249
    def list_objects(self, container, format='text', limit=10000, marker=None,
250
                     prefix=None, delimiter=None, path=None,
251
                     include_trashed=False, params={}, **headers):
252
        """returns a list with the container objects"""
253
        params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
254
                       'delimiter':delimiter, 'path':path})
255
        l = self._list('/' + container, format, params, **headers)
256
        #TODO support filter trashed with xml also
257
        if format != 'xml' and not include_trashed:
258
            l = self._filter_trashed(l)
259
        return l
260
    
261
    def create_container(self, container, **meta):
262
        """creates a container"""
263
        headers = {}
264
        for k,v in meta.items():
265
            headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
266
        status, header, data = self.put('/' + container, headers=headers)
267
        if status == 202:
268
            return False
269
        elif status != 201:
270
            raise Fault(data, int(status))
271
        return True
272
    
273
    def delete_container(self, container, params={}):
274
        """deletes a container"""
275
        return self.delete('/' + container, params=params)
276
    
277
    def retrieve_container_metadata(self, container, restricted=False, **params):
278
        """returns the container metadata"""
279
        prefix = 'x-container-meta-' if restricted else None
280
        return self._get_metadata('/%s' % container, prefix, params)
281
    
282
    def update_container_metadata(self, container, **meta):
283
        """unpdates the container metadata"""
284
        return self._update_metadata('/' + container, 'container', **meta)
285
        
286
    def delete_container_metadata(self, container, meta=[]):
287
        """deletes the container metadata"""
288
        path = '/%s' % (container)
289
        return self._delete_metadata(path, 'container', meta)
290
    
291
    # Storage Object Services
292
    
293
    def request_object(self, container, object, format='text', params={},
294
                        **headers):
295
        """returns tuple containing the status, headers and data response for an object request"""
296
        path = '/%s/%s' % (container, object)
297
        status, headers, data = self.get(path, format, headers, params)
298
        return status, headers, data
299
    
300
    def retrieve_object(self, container, object, format='text', params={},
301
                             **headers):
302
        """returns an object's data"""
303
        t = self.request_object(container, object, format, params, **headers)
304
        return t[2]
305
    
306
    def create_directory_marker(self, container, object):
307
        """creates a dierectory marker"""
308
        if not object:
309
            raise Fault('Directory markers have to be nested in a container')
310
        h = {'Content-Type':'application/directory'}
311
        return self.create_zero_length_object(container, object, **h)
312
    
313
    def create_directory_marker(self, container, object):
314
        """creates a dierectory marker"""
315
        return self.create_object(container, object, f=None)
316
    
317
    def create_object(self, container, object, f=stdin, format='text', meta={},
318
                      etag=None, content_type=None, content_encoding=None,
319
                      content_disposition=None, **headers):
320
        """creates a zero-length object"""
321
        path = '/%s/%s' % (container, object)
322
        for k, v  in headers.items():
323
            if not v:
324
                headers.pop(k)
325
        
326
        l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
327
        l = [elem for elem in l if eval(elem)]
328
        for elem in l:
329
            headers.update({elem:eval(elem)})
330
            
331
        for k,v in meta.items():
332
            headers['x-object-meta-%s' %k.strip()] = v.strip()
333
        data = f.read() if f else None
334
        return self.put(path, data, format, headers=headers)
335
    
336
    def create_zero_length_object(self, container, object, meta={}, etag=None,
337
                                  content_type=None, content_encoding=None,
338
                                  content_disposition=None, **headers):
339
        args = locals()
340
        for elem in ['self', 'container', 'headers']:
341
            args.pop(elem)
342
        args.update(headers)
343
        return self.create_object(container, f=None, **args)
344
    
345
    def update_object(self, container, object, f=stdin, offset=None, meta={},
346
                      content_length=None, content_type=None,
347
                      content_encoding=None, content_disposition=None,
348
                      **headers):
349
        path = '/%s/%s' % (container, object)
350
        for k, v  in headers.items():
351
            if not v:
352
                headers.pop(k)
353
        
354
        l = ['content_encoding', 'content_disposition', 'content_type',
355
             'content_length']
356
        l = [elem for elem in l if eval(elem)]
357
        for elem in l:
358
            headers.update({elem:eval(elem)})
359
            
360
        if 'content_range' not in headers.keys():
361
            if offset != None:
362
                headers['content_range'] = 'bytes %s-/*' % offset
363
            else:
364
                headers['content_range'] = 'bytes */*'
365
            
366
        for k,v in meta.items():
367
            headers['x-object-meta-%s' %k.strip()] = v.strip()
368
        data = f.read() if f else None
369
        return self.post(path, data, headers=headers)
370
    
371
    def _change_obj_location(self, src_container, src_object, dst_container,
372
                             dst_object, remove=False, public=False, **meta):
373
        path = '/%s/%s' % (dst_container, dst_object)
374
        headers = {}
375
        for k, v in meta.items():
376
            headers['x-object-meta-%s' % k] = v 
377
        if remove:
378
            headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
379
        else:
380
            headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
381
        self._set_public_header(headers, public)
382
        self.headers = headers if headers else None
383
        headers['content-length'] = 0
384
        return self.put(path, headers=headers)
385
    
386
    def copy_object(self, src_container, src_object, dst_container,
387
                             dst_object, public=False, **meta):
388
        return self._change_obj_location(src_container, src_object,
389
                                   dst_container, dst_object, False,
390
                                   public, **meta)
391
    
392
    def move_object(self, src_container, src_object, dst_container,
393
                             dst_object, public=False, **meta):
394
        return self._change_obj_location(src_container, src_object,
395
                                   dst_container, dst_object, True,
396
                                   public, **meta)
397
    
398
    def delete_object(self, container, object, params={}):
399
        return self.delete('/%s/%s' % (container, object), params=params)
400
    
401
    def retrieve_object_metadata(self, container, object, restricted=False,
402
                                 version=None):
403
        """
404
        set restricted to True to get only user defined metadata
405
        """
406
        path = '/%s/%s' % (container, object)
407
        prefix = 'x-object-meta-' if restricted else None
408
        params = {'version':version} if version else {}
409
        return self._get_metadata(path, prefix, params=params)
410
    
411
    def update_object_metadata(self, container, object, **meta):
412
        path = '/%s/%s' % (container, object)
413
        return self._update_metadata(path, 'object', **meta)
414
    
415
    def delete_object_metadata(self, container, object, meta=[]):
416
        path = '/%s/%s' % (container, object)
417
        return self._delete_metadata(path, 'object', meta)
418
    
419
class Pithos_Client(OOS_Client):
420
    """Pithos Storage Client. Extends OOS_Client"""
421
    
422
    def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
423
                          blocksize=1024):
424
        """perfomrs a chunked request"""
425
        http = HTTPConnection(self.host)
426
        
427
        # write header
428
        path = '/%s/%s%s' % (self.api, self.account, path)
429
        http.putrequest(method, path)
430
        http.putheader('x-auth-token', self.token)
431
        http.putheader('content-type', 'application/octet-stream')
432
        http.putheader('transfer-encoding', 'chunked')
433
        if headers:
434
            for header,value in headers.items():
435
                http.putheader(header, value)
436
        http.endheaders()
437
        
438
        # write body
439
        data = ''
440
        while True:
441
            if f.closed:
442
                break
443
            block = f.read(blocksize)
444
            if block == '':
445
                break
446
            data = '%s\r\n%s\r\n' % (hex(len(block)), block)
447
            try:
448
                http.send(data)
449
            except:
450
                #retry
451
                http.send(data)
452
        data = '0x0\r\n'
453
        try:
454
            http.send(data)
455
        except:
456
            #retry
457
            http.send(data)
458
        
459
        # get response
460
        resp = http.getresponse()
461
        
462
        headers = dict(resp.getheaders())
463
        
464
        if self.verbose:
465
            print '%d %s' % (resp.status, resp.reason)
466
            for key, val in headers.items():
467
                print '%s: %s' % (key.capitalize(), val)
468
            print
469
        
470
        length = resp.getheader('Content-length', None)
471
        data = resp.read(length)
472
        if self.debug:
473
            print data
474
            print
475
        
476
        if int(resp.status) in ERROR_CODES.keys():
477
            raise Fault(data, int(resp.status))
478
        
479
        #print '*',  resp.status, headers, data
480
        return resp.status, headers, data
481
    
482
    def _update_metadata(self, path, entity, **meta):
483
        """
484
        adds new and updates the values of previously set metadata
485
        """
486
        params = {'update':None}
487
        headers = {}
488
        prefix = 'x-%s-meta-' % entity
489
        for k,v in meta.items():
490
            k = '%s%s' % (prefix, k)
491
            headers[k] = v
492
        return self.post(path, headers=headers, params=params)
493
    
494
    def _delete_metadata(self, path, entity, meta=[]):
495
        """
496
        delete previously set metadata
497
        """
498
        params = {'update':None}
499
        headers = {}
500
        prefix = 'x-%s-meta-' % entity
501
        for m in meta:
502
            headers['%s%s' % (prefix, m)] = ''
503
        return self.post(path, headers=headers, params=params)
504
    
505
    # Storage Account Services
506
    
507
    def list_containers(self, format='text', if_modified_since=None,
508
                        if_unmodified_since=None, limit=1000, marker=None,
509
                        until=None):
510
        """returns a list with the account containers"""
511
        params = {'until':until} if until else None
512
        headers = {'if-modified-since':if_modified_since,
513
                   'if-unmodified-since':if_unmodified_since}
514
        return OOS_Client.list_containers(self, format=format, limit=limit,
515
                                          marker=marker, params=params,
516
                                          **headers)
517
    
518
    def retrieve_account_metadata(self, restricted=False, until=None):
519
        """returns the account metadata"""
520
        params = {'until':until} if until else {}
521
        return OOS_Client.retrieve_account_metadata(self, restricted=restricted,
522
                                                   **params)
523
    
524
    def set_account_groups(self, **groups):
525
        """create account groups"""
526
        headers = {}
527
        for key, val in groups.items():
528
            headers['x-account-group-%s' % key] = val
529
        params = {'update':None}
530
        return self.post('', headers=headers, params=params)
531
    
532
    def retrieve_account_groups(self):
533
        """returns the account groups"""
534
        meta = self.retrieve_account_metadata()
535
        prefix = 'x-account-group-'
536
        prefixlen = len(prefix)
537
        groups = {}
538
        for key, val in meta.items():
539
            if prefix and not key.startswith(prefix):
540
                continue
541
            elif prefix and key.startswith(prefix):
542
                key = key[prefixlen:]
543
            groups[key] = val
544
        return groups
545
    
546
    def unset_account_groups(self, groups=[]):
547
        """delete account groups"""
548
        headers = {}
549
        for elem in groups:
550
            headers['x-account-group-%s' % elem] = ''
551
        params = {'update':None}
552
        return self.post('', headers=headers, params=params)
553
    
554
    def reset_account_groups(self, **groups):
555
        """overrides account groups"""
556
        headers = {}
557
        for key, val in groups.items():
558
            headers['x-account-group-%s' % key] = val
559
        meta = self.retrieve_account_metadata()
560
        headers.update(meta)
561
        return self.post('', headers=headers)
562
    
563
    # Storage Container Services
564
    
565
    def list_objects(self, container, format='text', limit=10000, marker=None,
566
                     prefix=None, delimiter=None, path=None,
567
                     include_trashed=False, params={}, if_modified_since=None,
568
                     if_unmodified_since=None, meta={}, until=None):
569
        """returns a list with the container objects"""
570
        params = {'until':until, 'meta':meta}
571
        args = locals()
572
        for elem in ['self', 'container', 'params', 'until', 'meta']:
573
            args.pop(elem)
574
        return OOS_Client.list_objects(self, container, params=params, 
575
                                       **args)
576
    
577
    def retrieve_container_metadata(self, container, restricted=False,
578
                                    until=None):
579
        """returns container's metadata"""
580
        params = {'until':until} if until else {}
581
        return OOS_Client.retrieve_container_metadata(self, container,
582
                                                      restricted=restricted,
583
                                                      **params)
584
    
585
    def set_container_policies(self, container, **policies):
586
        """sets containers policies"""
587
        path = '/%s' % (container)
588
        headers = {}
589
        print ''
590
        for key, val in policies.items():
591
            headers['x-container-policy-%s' % key] = val
592
        return self.post(path, headers=headers)
593
    
594
    def delete_container(self, container, until=None):
595
        """deletes a container or the container history until the date provided"""
596
        params = {'until':until} if until else {}
597
        return OOS_Client.delete_container(self, container, params)
598
    
599
    # Storage Object Services
600
    
601
    def retrieve_object(self, container, object, params={}, format='text', range=None,
602
                        if_range=None, if_match=None, if_none_match=None,
603
                        if_modified_since=None, if_unmodified_since=None,
604
                        **headers):
605
        """returns an object"""
606
        headers={}
607
        l = ['range', 'if_range', 'if_match', 'if_none_match',
608
             'if_modified_since', 'if_unmodified_since']
609
        l = [elem for elem in l if eval(elem)]
610
        for elem in l:
611
            headers.update({elem:eval(elem)})
612
        return OOS_Client.retrieve_object(self, container, object, format=format,
613
                                          params=params, **headers)
614
    
615
    def retrieve_object_version(self, container, object, version, detail=False,
616
                                range=None, if_range=None, if_match=None,
617
                                if_none_match=None, if_modified_since=None,
618
                                if_unmodified_since=None):
619
        """returns a specific object version"""
620
        args = locals()
621
        l = ['self', 'container', 'object']
622
        for elem in l:
623
            args.pop(elem)
624
        params = {'version':version}
625
        return self.retrieve_object(container, object, params, **args)
626
    
627
    def retrieve_object_versionlist(self, container, object, range=None,
628
                                    if_range=None, if_match=None,
629
                                    if_none_match=None, if_modified_since=None,
630
                                    if_unmodified_since=None):
631
        """returns the object version list"""
632
        args = locals()
633
        l = ['self', 'container', 'object']
634
        for elem in l:
635
            args.pop(elem)
636
            
637
        return self.retrieve_object_version(container, object, version='list',
638
                                            detail=True, **args)
639
    
640
    def create_zero_length_object(self, container, object, meta={},
641
                      etag=None, content_type=None, content_encoding=None,
642
                      content_disposition=None, x_object_manifest=None,
643
                      x_object_sharing=None, x_object_public=None):
644
        """createas a zero length object"""
645
        args = locals()
646
        for elem in ['self', 'container', 'object']:
647
            args.pop(elem)
648
        return OOS_Client.create_zero_length_object(self, container, object,
649
                                                    **args)
650
    
651
    def create_object(self, container, object, f=stdin, meta={},
652
                      etag=None, content_type=None, content_encoding=None,
653
                      content_disposition=None, x_object_manifest=None,
654
                      x_object_sharing=None, x_object_public=None):
655
        """creates an object"""
656
        args = locals()
657
        for elem in ['self', 'container', 'object']:
658
            args.pop(elem)
659
        return OOS_Client.create_object(self, container, object, **args)
660
        
661
    def create_object_using_chunks(self, container, object, f=stdin,
662
                                    blocksize=1024, meta={}, etag=None,
663
                                    content_type=None, content_encoding=None,
664
                                    content_disposition=None, 
665
                                    x_object_sharing=None,
666
                                    x_object_manifest=None, 
667
                                    x_object_public=None):
668
        """creates an object (incremental upload)"""
669
        path = '/%s/%s' % (container, object)
670
        headers = {}
671
        l = ['etag', 'content_type', 'content_encoding', 'content_disposition', 
672
             'x_object_sharing', 'x_object_manifest', 'x_object_public']
673
        l = [elem for elem in l if eval(elem)]
674
        for elem in l:
675
            headers.update({elem:eval(elem)})
676
        
677
        for k,v in meta.items():
678
            headers['x-object-meta-%s' %k.strip()] = v.strip()
679
        
680
        return self._chunked_transfer(path, 'PUT', f, headers=headers,
681
                                      blocksize=blocksize)
682
    
683
    def create_object_by_hashmap(container, object, f=stdin, format='json',
684
                                 meta={}, etag=None, content_encoding=None,
685
                                 content_disposition=None, content_type=None,
686
                                 x_object_sharing=None, x_object_manifest=None,
687
                                 x_object_public = None):
688
        """creates an object by uploading hashes representing data instead of data"""
689
        args = locals()
690
        for elem in ['self', 'container', 'object']:
691
            args.pop(elem)
692
            
693
        data = f.read() if f else None
694
        if data and format == 'json':
695
            try:
696
                data = eval(data)
697
                data = json.dumps(data)
698
            except SyntaxError:
699
                raise Fault('Invalid formatting')
700
        
701
        #TODO check with xml
702
        return self.create_object(container, object, **args)
703
    
704
    def create_manifestation(self, container, object, manifest):
705
        """creates a manifestation"""
706
        headers={'x_object_manifest':manifest}
707
        return self.create_object(container, object, f=None, **headers)
708
    
709
    def update_object(self, container, object, f=stdin, offset=None, meta={},
710
                      content_length=None, content_type=None, content_range=None,
711
                      content_encoding=None, content_disposition=None,
712
                      x_object_bytes=None, x_object_manifest=None,
713
                      x_object_sharing=None, x_object_public=None):
714
        """updates an object"""
715
        spath = '/%s/%s' % (container, object)
716
        args = locals()
717
        for elem in ['self', 'container', 'object']:
718
            args.pop(elem)
719
        
720
        return OOS_Client.update_object(self, container, object, **args)
721
        
722
    def update_object_using_chunks(self, container, object, f=stdin,
723
                                    blocksize=1024, offset=None, meta={},
724
                                    content_type=None, content_encoding=None,
725
                                    content_disposition=None, x_object_bytes=None,
726
                                    x_object_manifest=None, x_object_sharing=None,
727
                                    x_object_public=None):
728
        """updates an object (incremental upload)"""
729
        path = '/%s/%s' % (container, object)
730
        headers = {}
731
        l = ['content_type', 'content_encoding', 'content_disposition',
732
             'x_object_bytes', 'x_object_manifest', 'x_object_sharing',
733
             'x_object_public']
734
        l = [elem for elem in l if eval(elem)]
735
        for elem in l:
736
            headers.update({elem:eval(elem)})
737
        
738
        if offset != None:
739
            headers['content_range'] = 'bytes %s-/*' % offset
740
        else:
741
            headers['content_range'] = 'bytes */*'
742
        
743
        for k,v in meta.items():
744
            headers['x-object-meta-%s' %k.strip()] = v.strip()
745
        
746
        return self._chunked_transfer(path, 'POST', f, headers=headers,
747
                                      blocksize=blocksize)
748
    
749
    def delete_object(self, container, object, until=None):
750
        """deletes an object or the object history until the date provided"""
751
        params = {'until':until} if until else {}
752
        return OOS_Client.delete_object(self, container, object, params)
753
    
754
    def trash_object(self, container, object):
755
        """trashes an object"""
756
        path = '/%s/%s' % (container, object)
757
        meta = {'trash':'true'}
758
        return self._update_metadata(path, 'object', **meta)
759
    
760
    def restore_object(self, container, object):
761
        """restores a trashed object"""
762
        return self.delete_object_metadata(container, object, ['trash'])
763
    
764
    def _set_public_header(self, headers, public=False):
765
        """sets the public header"""
766
        if not headers:
767
            headers = {}
768
        if public == None:
769
            return
770
        else:
771
            headers['x-object-public'] = public if public else ''
772
    
773
    def publish_object(self, container, object):
774
        """sets a previously created object publicly accessible"""
775
        path = '/%s/%s' % (container, object)
776
        headers = {'content_range':'bytes */*'}
777
        self._set_public_header(headers, public=True)
778
        return self.post(path, headers=headers)
779
    
780
    def unpublish_object(self, container, object):
781
        """unpublish an object"""
782
        path = '/%s/%s' % (container, object)
783
        headers = {'content_range':'bytes */*'}
784
        self._set_public_header(headers, public=False)
785
        return self.post(path, headers=headers)