Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ f0eacc2c

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