Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ 7812e1f9

History | View | Annotate | Download (32.6 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_object(self, container, object, f=stdin, format='text', meta={},
314
                      etag=None, content_type=None, content_encoding=None,
315
                      content_disposition=None, **headers):
316
        """creates a zero-length object"""
317
        path = '/%s/%s' % (container, object)
318
        for k, v  in headers.items():
319
            if not v:
320
                headers.pop(k)
321
        
322
        l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
323
        l = [elem for elem in l if eval(elem)]
324
        for elem in l:
325
            headers.update({elem:eval(elem)})
326
            
327
        for k,v in meta.items():
328
            headers['x-object-meta-%s' %k.strip()] = v.strip()
329
        data = f.read() if f else None
330
        return self.put(path, data, format, headers=headers)
331
    
332
    def create_zero_length_object(self, container, object, meta={}, etag=None,
333
                                  content_type=None, content_encoding=None,
334
                                  content_disposition=None, **headers):
335
        args = locals()
336
        for elem in ['self', 'container', 'headers']:
337
            args.pop(elem)
338
        args.update(headers)
339
        return self.create_object(container, f=None, **args)
340
    
341
    def update_object(self, container, object, f=stdin, offset=None, meta={},
342
                      content_length=None, content_type=None,
343
                      content_encoding=None, content_disposition=None,
344
                      **headers):
345
        path = '/%s/%s' % (container, object)
346
        for k, v  in headers.items():
347
            if not v:
348
                headers.pop(k)
349
        
350
        l = ['content_encoding', 'content_disposition', 'content_type',
351
             'content_length']
352
        l = [elem for elem in l if eval(elem)]
353
        for elem in l:
354
            headers.update({elem:eval(elem)})
355
            
356
        if 'content_range' not in headers.keys():
357
            if offset != None:
358
                headers['content_range'] = 'bytes %s-/*' % offset
359
            else:
360
                headers['content_range'] = 'bytes */*'
361
            
362
        for k,v in meta.items():
363
            headers['x-object-meta-%s' %k.strip()] = v.strip()
364
        data = f.read() if f else None
365
        return self.post(path, data, headers=headers)
366
    
367
    def _change_obj_location(self, src_container, src_object, dst_container,
368
                             dst_object, remove=False, meta={}, **headers):
369
        path = '/%s/%s' % (dst_container, dst_object)
370
        headers = {} if not headers else headers
371
        for k, v in meta.items():
372
            headers['x-object-meta-%s' % k] = v 
373
        if remove:
374
            headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
375
        else:
376
            headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
377
        headers['content_length'] = 0
378
        return self.put(path, headers=headers)
379
    
380
    def copy_object(self, src_container, src_object, dst_container, dst_object,
381
                    meta, **headers):
382
        """copies an object"""
383
        return self._change_obj_location(src_container, src_object,
384
                                   dst_container, dst_object, remove=False,
385
                                   meta=meta, **headers)
386
    
387
    def move_object(self, src_container, src_object, dst_container,
388
                             dst_object, meta={}, **headers):
389
        """moves an object"""
390
        return self._change_obj_location(src_container, src_object,
391
                                         dst_container, dst_object, remove=True,
392
                                         meta=meta, **headers)
393
    
394
    def delete_object(self, container, object, params={}):
395
        """deletes an object"""
396
        return self.delete('/%s/%s' % (container, object), params=params)
397
    
398
    def retrieve_object_metadata(self, container, object, restricted=False,
399
                                 version=None):
400
        """
401
        set restricted to True to get only user defined metadata
402
        """
403
        path = '/%s/%s' % (container, object)
404
        prefix = 'x-object-meta-' if restricted else None
405
        params = {'version':version} if version else {}
406
        return self._get_metadata(path, prefix, params=params)
407
    
408
    def update_object_metadata(self, container, object, **meta):
409
        path = '/%s/%s' % (container, object)
410
        return self._update_metadata(path, 'object', **meta)
411
    
412
    def delete_object_metadata(self, container, object, meta=[]):
413
        path = '/%s/%s' % (container, object)
414
        return self._delete_metadata(path, 'object', meta)
415
    
416
class Pithos_Client(OOS_Client):
417
    """Pithos Storage Client. Extends OOS_Client"""
418
    
419
    def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
420
                          blocksize=1024):
421
        """perfomrs a chunked request"""
422
        http = HTTPConnection(self.host)
423
        
424
        # write header
425
        path = '/%s/%s%s' % (self.api, self.account, path)
426
        http.putrequest(method, path)
427
        http.putheader('x-auth-token', self.token)
428
        http.putheader('content-type', 'application/octet-stream')
429
        http.putheader('transfer-encoding', 'chunked')
430
        if headers:
431
            for header,value in headers.items():
432
                http.putheader(header, value)
433
        http.endheaders()
434
        
435
        # write body
436
        data = ''
437
        while True:
438
            if f.closed:
439
                break
440
            block = f.read(blocksize)
441
            if block == '':
442
                break
443
            data = '%s\r\n%s\r\n' % (hex(len(block)), block)
444
            try:
445
                http.send(data)
446
            except:
447
                #retry
448
                http.send(data)
449
        data = '0x0\r\n'
450
        try:
451
            http.send(data)
452
        except:
453
            #retry
454
            http.send(data)
455
        
456
        # get response
457
        resp = http.getresponse()
458
        
459
        headers = dict(resp.getheaders())
460
        
461
        if self.verbose:
462
            print '%d %s' % (resp.status, resp.reason)
463
            for key, val in headers.items():
464
                print '%s: %s' % (key.capitalize(), val)
465
            print
466
        
467
        length = resp.getheader('Content-length', None)
468
        data = resp.read(length)
469
        if self.debug:
470
            print data
471
            print
472
        
473
        if int(resp.status) in ERROR_CODES.keys():
474
            raise Fault(data, int(resp.status))
475
        
476
        #print '*',  resp.status, headers, data
477
        return resp.status, headers, data
478
    
479
    def _update_metadata(self, path, entity, **meta):
480
        """
481
        adds new and updates the values of previously set metadata
482
        """
483
        params = {'update':None}
484
        headers = {}
485
        prefix = 'x-%s-meta-' % entity
486
        for k,v in meta.items():
487
            k = '%s%s' % (prefix, k)
488
            headers[k] = v
489
        return self.post(path, headers=headers, params=params)
490
    
491
    def _delete_metadata(self, path, entity, meta=[]):
492
        """
493
        delete previously set metadata
494
        """
495
        params = {'update':None}
496
        headers = {}
497
        prefix = 'x-%s-meta-' % entity
498
        for m in meta:
499
            headers['%s%s' % (prefix, m)] = ''
500
        return self.post(path, headers=headers, params=params)
501
    
502
    # Storage Account Services
503
    
504
    def list_containers(self, format='text', if_modified_since=None,
505
                        if_unmodified_since=None, limit=1000, marker=None,
506
                        until=None):
507
        """returns a list with the account containers"""
508
        params = {'until':until} if until else None
509
        headers = {'if-modified-since':if_modified_since,
510
                   'if-unmodified-since':if_unmodified_since}
511
        return OOS_Client.list_containers(self, format=format, limit=limit,
512
                                          marker=marker, params=params,
513
                                          **headers)
514
    
515
    def retrieve_account_metadata(self, restricted=False, until=None):
516
        """returns the account metadata"""
517
        params = {'until':until} if until else {}
518
        return OOS_Client.retrieve_account_metadata(self, restricted=restricted,
519
                                                   **params)
520
    
521
    def set_account_groups(self, **groups):
522
        """create account groups"""
523
        headers = {}
524
        for key, val in groups.items():
525
            headers['x-account-group-%s' % key] = val
526
        params = {'update':None}
527
        return self.post('', headers=headers, params=params)
528
    
529
    def retrieve_account_groups(self):
530
        """returns the account groups"""
531
        meta = self.retrieve_account_metadata()
532
        prefix = 'x-account-group-'
533
        prefixlen = len(prefix)
534
        groups = {}
535
        for key, val in meta.items():
536
            if prefix and not key.startswith(prefix):
537
                continue
538
            elif prefix and key.startswith(prefix):
539
                key = key[prefixlen:]
540
            groups[key] = val
541
        return groups
542
    
543
    def unset_account_groups(self, groups=[]):
544
        """delete account groups"""
545
        headers = {}
546
        for elem in groups:
547
            headers['x-account-group-%s' % elem] = ''
548
        params = {'update':None}
549
        return self.post('', headers=headers, params=params)
550
    
551
    def reset_account_groups(self, **groups):
552
        """overrides account groups"""
553
        headers = {}
554
        for key, val in groups.items():
555
            headers['x-account-group-%s' % key] = val
556
        meta = self.retrieve_account_metadata()
557
        headers.update(meta)
558
        return self.post('', headers=headers)
559
    
560
    # Storage Container Services
561
    
562
    def list_objects(self, container, format='text', limit=10000, marker=None,
563
                     prefix=None, delimiter=None, path=None,
564
                     include_trashed=False, params={}, if_modified_since=None,
565
                     if_unmodified_since=None, meta={}, until=None):
566
        """returns a list with the container objects"""
567
        params = {'until':until, 'meta':meta}
568
        args = locals()
569
        for elem in ['self', 'container', 'params', 'until', 'meta']:
570
            args.pop(elem)
571
        return OOS_Client.list_objects(self, container, params=params, 
572
                                       **args)
573
    
574
    def retrieve_container_metadata(self, container, restricted=False,
575
                                    until=None):
576
        """returns container's metadata"""
577
        params = {'until':until} if until else {}
578
        return OOS_Client.retrieve_container_metadata(self, container,
579
                                                      restricted=restricted,
580
                                                      **params)
581
    
582
    def set_container_policies(self, container, **policies):
583
        """sets containers policies"""
584
        path = '/%s' % (container)
585
        headers = {}
586
        print ''
587
        for key, val in policies.items():
588
            headers['x-container-policy-%s' % key] = val
589
        return self.post(path, headers=headers)
590
    
591
    def delete_container(self, container, until=None):
592
        """deletes a container or the container history until the date provided"""
593
        params = {'until':until} if until else {}
594
        return OOS_Client.delete_container(self, container, params)
595
    
596
    # Storage Object Services
597
    
598
    def retrieve_object(self, container, object, params={}, format='text', range=None,
599
                        if_range=None, if_match=None, if_none_match=None,
600
                        if_modified_since=None, if_unmodified_since=None,
601
                        **headers):
602
        """returns an object"""
603
        headers={}
604
        l = ['range', 'if_range', 'if_match', 'if_none_match',
605
             'if_modified_since', 'if_unmodified_since']
606
        l = [elem for elem in l if eval(elem)]
607
        for elem in l:
608
            headers.update({elem:eval(elem)})
609
        return OOS_Client.retrieve_object(self, container, object, format=format,
610
                                          params=params, **headers)
611
    
612
    def retrieve_object_version(self, container, object, version, detail=False,
613
                                range=None, if_range=None, if_match=None,
614
                                if_none_match=None, if_modified_since=None,
615
                                if_unmodified_since=None):
616
        """returns a specific object version"""
617
        args = locals()
618
        l = ['self', 'container', 'object']
619
        for elem in l:
620
            args.pop(elem)
621
        params = {'version':version}
622
        return self.retrieve_object(container, object, params, **args)
623
    
624
    def retrieve_object_versionlist(self, container, object, range=None,
625
                                    if_range=None, if_match=None,
626
                                    if_none_match=None, if_modified_since=None,
627
                                    if_unmodified_since=None):
628
        """returns the object version list"""
629
        args = locals()
630
        l = ['self', 'container', 'object']
631
        for elem in l:
632
            args.pop(elem)
633
            
634
        return self.retrieve_object_version(container, object, version='list',
635
                                            detail=True, **args)
636
    
637
    def create_zero_length_object(self, container, object, meta={},
638
                      etag=None, content_type=None, content_encoding=None,
639
                      content_disposition=None, x_object_manifest=None,
640
                      x_object_sharing=None, x_object_public=None):
641
        """createas a zero length object"""
642
        args = locals()
643
        for elem in ['self', 'container', 'object']:
644
            args.pop(elem)
645
        return OOS_Client.create_zero_length_object(self, container, object,
646
                                                    **args)
647
    
648
    def create_object(self, container, object, f=stdin, meta={},
649
                      etag=None, content_type=None, content_encoding=None,
650
                      content_disposition=None, x_object_manifest=None,
651
                      x_object_sharing=None, x_object_public=None):
652
        """creates an object"""
653
        args = locals()
654
        for elem in ['self', 'container', 'object']:
655
            args.pop(elem)
656
        return OOS_Client.create_object(self, container, object, **args)
657
        
658
    def create_object_using_chunks(self, container, object, f=stdin,
659
                                    blocksize=1024, meta={}, etag=None,
660
                                    content_type=None, content_encoding=None,
661
                                    content_disposition=None, 
662
                                    x_object_sharing=None,
663
                                    x_object_manifest=None, 
664
                                    x_object_public=None):
665
        """creates an object (incremental upload)"""
666
        path = '/%s/%s' % (container, object)
667
        headers = {}
668
        l = ['etag', 'content_type', 'content_encoding', 'content_disposition', 
669
             'x_object_sharing', 'x_object_manifest', '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
        for k,v in meta.items():
675
            headers['x-object-meta-%s' %k.strip()] = v.strip()
676
        
677
        return self._chunked_transfer(path, 'PUT', f, headers=headers,
678
                                      blocksize=blocksize)
679
    
680
    def create_object_by_hashmap(container, object, f=stdin, format='json',
681
                                 meta={}, etag=None, content_encoding=None,
682
                                 content_disposition=None, content_type=None,
683
                                 x_object_sharing=None, x_object_manifest=None,
684
                                 x_object_public = None):
685
        """creates an object by uploading hashes representing data instead of data"""
686
        args = locals()
687
        for elem in ['self', 'container', 'object']:
688
            args.pop(elem)
689
            
690
        data = f.read() if f else None
691
        if data and format == 'json':
692
            try:
693
                data = eval(data)
694
                data = json.dumps(data)
695
            except SyntaxError:
696
                raise Fault('Invalid formatting')
697
        
698
        #TODO check with xml
699
        return self.create_object(container, object, **args)
700
    
701
    def create_manifestation(self, container, object, manifest):
702
        """creates a manifestation"""
703
        headers={'x_object_manifest':manifest}
704
        return self.create_object(container, object, f=None, **headers)
705
    
706
    def update_object(self, container, object, f=stdin, offset=None, meta={},
707
                      content_length=None, content_type=None, content_range=None,
708
                      content_encoding=None, content_disposition=None,
709
                      x_object_bytes=None, x_object_manifest=None,
710
                      x_object_sharing=None, x_object_public=None):
711
        """updates an object"""
712
        spath = '/%s/%s' % (container, object)
713
        args = locals()
714
        for elem in ['self', 'container', 'object']:
715
            args.pop(elem)
716
        
717
        return OOS_Client.update_object(self, container, object, **args)
718
        
719
    def update_object_using_chunks(self, container, object, f=stdin,
720
                                    blocksize=1024, offset=None, meta={},
721
                                    content_type=None, content_encoding=None,
722
                                    content_disposition=None, x_object_bytes=None,
723
                                    x_object_manifest=None, x_object_sharing=None,
724
                                    x_object_public=None):
725
        """updates an object (incremental upload)"""
726
        path = '/%s/%s' % (container, object)
727
        headers = {}
728
        l = ['content_type', 'content_encoding', 'content_disposition',
729
             'x_object_bytes', 'x_object_manifest', 'x_object_sharing',
730
             'x_object_public']
731
        l = [elem for elem in l if eval(elem)]
732
        for elem in l:
733
            headers.update({elem:eval(elem)})
734
        
735
        if offset != None:
736
            headers['content_range'] = 'bytes %s-/*' % offset
737
        else:
738
            headers['content_range'] = 'bytes */*'
739
        
740
        for k,v in meta.items():
741
            headers['x-object-meta-%s' %k.strip()] = v.strip()
742
        
743
        return self._chunked_transfer(path, 'POST', f, headers=headers,
744
                                      blocksize=blocksize)
745
    
746
    def delete_object(self, container, object, until=None):
747
        """deletes an object or the object history until the date provided"""
748
        params = {'until':until} if until else {}
749
        return OOS_Client.delete_object(self, container, object, params)
750
    
751
    def trash_object(self, container, object):
752
        """trashes an object"""
753
        path = '/%s/%s' % (container, object)
754
        meta = {'trash':'true'}
755
        return self._update_metadata(path, 'object', **meta)
756
    
757
    def restore_object(self, container, object):
758
        """restores a trashed object"""
759
        return self.delete_object_metadata(container, object, ['trash'])
760
    
761
    def publish_object(self, container, object):
762
        """sets a previously created object publicly accessible"""
763
        path = '/%s/%s' % (container, object)
764
        headers = {'content_range':'bytes */*'}
765
        headers['x_object_public'] = True
766
        return self.post(path, headers=headers)
767
    
768
    def unpublish_object(self, container, object):
769
        """unpublish an object"""
770
        path = '/%s/%s' % (container, object)
771
        headers = {'content_range':'bytes */*'}
772
        headers['x_object_public'] = False
773
        return self.post(path, headers=headers)
774
    
775
    def _change_obj_location(self, src_container, src_object, dst_container,
776
                             dst_object, remove=False, meta={}, **headers):
777
        path = '/%s/%s' % (dst_container, dst_object)
778
        headers = {} if not headers else headers
779
        for k, v in meta.items():
780
            headers['x-object-meta-%s' % k] = v 
781
        if remove:
782
            headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
783
        else:
784
            headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
785
        headers['content_length'] = 0
786
        return self.put(path, headers=headers)
787
    
788
    def copy_object(self, src_container, src_object, dst_container, dst_object,
789
                    meta={}, public=False, version=None):
790
        """copies an object"""
791
        headers = {}
792
        headers['x_object_public'] = public
793
        if version:
794
            headers['x_object_version'] = version
795
        return OOS_Client.copy_object(self, src_container, src_object,
796
                                      dst_container, dst_object, meta=meta,
797
                                      **headers)
798
    
799
    def move_object(self, src_container, src_object, dst_container,
800
                             dst_object, meta={}, public=False, version=None):
801
        """moves an object"""
802
        headers = {}
803
        headers['x_object_public'] = public
804
        if version:
805
            headers['x_object_version'] = version
806
        return OOS_Client.move_object(self, src_container, src_object,
807
                                      dst_container, dst_object, meta=meta,
808
                                      **headers)