Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ 25c3841c

History | View | Annotate | Download (28.9 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

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

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

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

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