Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ ae75584f

History | View | Annotate | Download (33.8 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 urllib
42
import pithos.api.faults
43

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

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

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

    
833
def _encode_headers(headers):
834
    h = {}
835
    for k, v in headers.items():
836
        k = urllib.quote(k)
837
        if v and type(v) == types.StringType:
838
            v = urllib.quote(v, '/=,-* :"')
839
        h[k] = v
840
    return h