Rewrite client library function for updating metadata using update POST parameter
[pithos] / tools / backendfs.py
1 #!/usr/bin/env python
2
3 from django.core.management import setup_environ
4 from pithos import settings
5 setup_environ(settings)
6
7 from functools import partial
8 from getpass import getuser
9 from errno import EACCES, EBADF, EINVAL, EISDIR, ENOENT, ENOTDIR, ENOTEMPTY
10 from stat import S_IFDIR, S_IFREG
11 from sys import argv
12 from time import time
13
14 from pithos.api.util import hashmap_hash
15 from pithos.backends import backend
16 from pithos.lib.fuse import FUSE, FuseOSError, Operations, LoggingMixIn
17
18
19 epoch = int(time())
20
21
22 class BackendProxy(object):
23     """A proxy object that always passes user and account as first args."""
24     
25     def __init__(self, backend, user, account):
26         self.backend = backend
27         self.user = user
28         self.account = account
29     
30     def __getattr__(self, name):
31         func = getattr(self.backend, name)
32         return partial(func, self.user, self.account)
33
34
35 def blocksplit(data, blocksize):
36     """An iterator that splits data into blocks of size `blocksize`."""
37     
38     while data:
39         yield data[:blocksize]
40         data = data[blocksize:]
41
42
43 class BackendFS(LoggingMixIn, Operations):
44     def __init__(self, account):
45         self.user = account
46         self.account = account
47         self.backend = BackendProxy(backend, self.user, self.account)
48     
49     def create(self, path, mode, fi=None):
50         container, sep, object = path[1:].partition('/')
51         if not object:
52             raise FuseOSError(EACCES)
53         
54         hashmap = []
55         meta = {'hash': hashmap_hash(hashmap)}
56         self.backend.update_object_hashmap(container, object, 0, hashmap,
57                                             meta, True)
58         return 0
59     
60     def getattr(self, path, fh=None):
61         container, sep, object = path[1:].partition('/')
62         if not container:
63             # Root level
64             containers = self.backend.list_containers()
65             return {
66                 'st_mode': (S_IFDIR | 0755),
67                 'st_ctime': epoch,
68                 'st_mtime': epoch,
69                 'st_atime': epoch,
70                 'st_nlink': 2 + len(containers)}
71         elif not object:
72             # Container level
73             try:
74                 meta = self.backend.get_container_meta(container)
75             except NameError:
76                 raise FuseOSError(ENOENT)
77             
78             return {
79                 'st_mode': (S_IFDIR | 0755),
80                 'st_ctime': epoch,
81                 'st_mtime': meta['modified'],
82                 'st_atime': meta['modified'],
83                 'st_nlink': 2 + meta['count']}
84         else:
85             # Object level
86             try:
87                 meta = self.backend.get_object_meta(container, object)
88             except NameError:
89                 raise FuseOSError(ENOENT)
90             
91             return {
92                 'st_mode': (S_IFREG | 0644),
93                 'st_ctime': epoch,
94                 'st_mtime': meta['modified'],
95                 'st_atime': meta['modified'],
96                 'st_nlink': 1,
97                 'st_size': meta['bytes']}
98     
99     def mkdir(self, path, mode):
100         container, sep, object = path[1:].partition('/')
101         if object:
102             raise FuseOSError(EACCES)
103         backend.put_container(self.user, self.account, container)
104     
105     def read(self, path, nbyte, offset, fh):
106         container, sep, object = path[1:].partition('/')
107         if not object:
108             raise FuseOSError(EBADF)
109         
110         # XXX This implementation is inefficient,
111         # it always reads all the blocks
112         size, hashmap = self.backend.get_object_hashmap(container, object)
113         buf = []
114         for hash in hashmap:
115             buf.append(backend.get_block(hash))
116         data = ''.join(buf)[:size]
117         return data[offset:offset + nbyte]
118     
119     def readdir(self, path, fh):
120         container, sep, object = path[1:].partition('/')
121         if not container:
122             # Root level
123             containers = [c[0] for c in self.backend.list_containers()]
124             return ['.', '..'] + containers
125         else:
126             # Container level
127             objects = [o[0] for o in self.backend.list_objects(container)]
128             return ['.', '..'] + objects
129     
130     def rmdir(self, path):
131         container, sep, object = path[1:].partition('/')
132         if object:
133             raise FuseOSError(ENOTDIR)
134         
135         try:
136             self.backend.delete_container(container)
137         except NameError:
138             raise FuseOSError(ENOENT)
139         except IndexError:
140             raise FuseOSError(ENOTEMPTY)
141     
142     def truncate(self, path, length, fh=None):
143         container, sep, object = path[1:].partition('/')
144         if not object:
145             raise FuseOSError(EISDIR)
146         
147         size, hashmap = self.backend.get_object_hashmap(container, object)
148         if length > size:
149             raise FuseOSError(EINVAL)   # Extension not supported
150         
151         div, mod = divmod(size, backend.block_size)
152         nblocks = div + 1 if mod else div
153         meta = {'hash': hashmap_hash(hashmap)}
154         self.backend.update_object_hashmap(container, object, size,
155                                             hashmap[:nblocks], meta, True)
156     
157     def unlink(self, path):
158         container, sep, object = path[1:].partition('/')
159         if not object:
160             raise FuseOSError(EACCES)
161         self.backend.delete_object(container, object)
162     
163     def write(self, path, data, offset, fh):
164         container, sep, object = path[1:].partition('/')
165         if not object:
166             raise FuseOSError(EBADF)
167         
168         hashmap = []
169         for block in blocksplit(data, backend.block_size):
170             hashmap.append(backend.put_block(block))
171         meta = {'hash': hashmap_hash(hashmap)}
172         self.backend.update_object_hashmap(container, object, len(data),
173                                             hashmap, meta, True)
174         return len(data)
175
176
177 if __name__ == "__main__":
178     if len(argv) != 2:
179         print 'usage: %s <mountpoint>' % argv[0]
180         exit(1)
181     account = getuser()
182     fuse = FUSE(BackendFS(account), argv[1], foreground=True)