069f9256f426c00e172763ec928ff3f57845649c
[invirt/packages/python-routefs.git] / routefs / __init__.py
1 """
2 RouteFS is a base class for developing read-only FUSE filesystems that
3 lets you focus on the directory tree instead of the system calls.
4
5 RouteFS uses the Routes library developed for Pylons. URLs were
6 inspired by filesystems, and now you can have filesystems inspired by
7 URLs.
8
9 When developing a descendent of RouteFS, any methods defined in that
10 class are considered "controllers", and receive any other parameters
11 specified by the URL as keyword arguments.
12 """
13
14 import fuse
15 import routes
16 import errno
17 import stat
18
19 fuse.fuse_python_api = (0, 2)
20
21 class RouteStat(fuse.Stat):
22     """
23     RouteStat is a descendent of fuse.Stat, defined to make sure that
24     all of the necessary attributes are always defined
25     """
26     def __init__(self):
27         self.st_mode = 0
28         self.st_ino = 0
29         self.st_dev = 0
30         self.st_nlink = 0
31         self.st_uid = 0
32         self.st_gid = 0
33         self.st_size = 0
34         self.st_atime = 0
35         self.st_mtime = 0
36         self.st_ctime = 0
37
38 class RouteMeta(type):
39     """
40     Metaclass to calculate controller methods
41     
42     Routes needs to be pre-seeded with a list of "controllers". For
43     all descendents of RouteFS, the list of controllers is defined to
44     be any non-private methods of the class that were not in the
45     RouteFS class.
46     """
47     def __init__(cls, classname, bases, dict_):
48         super(RouteMeta, cls).__init__(classname, bases, dict_)
49         if bases != (fuse.Fuse,):
50             new_funcs = set(dict_.keys()).difference(dir(RouteFS))
51             cls.controllers([func for func in new_funcs \
52                                  if not func.startswith('_')])
53
54 class RouteFS(fuse.Fuse):
55     """
56     RouteFS: Web 2.0 for filesystems
57     """
58     __metaclass__ = RouteMeta
59     def __init__(self, *args, **kwargs):
60         super(RouteFS, self).__init__(*args, **kwargs)
61         
62         self.map = self.make_map()
63         self.map.create_regs(self.controller_list)
64         
65     def make_map(self):
66         """
67         This method should be overridden by descendents of RouteFS to
68         define the routing for the filesystem
69         """
70         m = routes.Mapper()
71         
72         m.connect(':controller')
73         
74         return m
75     
76     @classmethod
77     def controllers(cls, lst):
78         cls.controller_list = lst
79     
80     def _get_file(self, path):
81         """
82         Find the filesystem entry object for a given path
83         """
84         match = self.map.match(path)
85         if match is None:
86             return NoEntry()
87         controller = match.pop('controller')
88         result = getattr(self, controller)(**match)
89         if result is None:
90             return NoEntry()
91         if type(result) is str:
92             result = File(result)
93         if type(result) is list:
94             result = Directory(result)
95         return result
96     
97     def readdir(self, path, offset):
98         """
99         If the path referred to is a directory, return the elements of
100         that diectory
101         """
102         return self._get_file(path).readdir(offset)
103     
104     def getattr(self, path):
105         """
106         Return the stat information for a path
107         
108         The stat information for a directory, symlink, or file is
109         predetermined based on which it is.
110         """
111         return self._get_file(path).getattr()
112     
113     def read(self, path, length, offset):
114         """
115         If the path specified is a file, return the requested portion
116         of the file
117         """
118         return self._get_file(path).read(length, offset)
119     
120     def readlink(self, path):
121         """
122         If the path specified is a symlink, return the target
123         """
124         return self._get_file(path).readlink()
125     
126     def write(self, path, buf, offset):
127         """
128         If the path specified is a file, call the appropriate member 
129         on the file
130         """
131         return self._get_file(path).write(buf, offset)
132
133 class TreeKey(object):
134     def getattr(self):
135         return -errno.EINVAL
136     def readdir(self, offset):
137         return -errno.EINVAL
138     def read(self, length, offset):
139         return -errno.EINVAL
140     def readlink(self):
141         return -errno.EINVAL
142     def write(self, length, offset):
143         return -errno.EINVAL
144
145 class NoEntry(TreeKey):
146     def getattr(self):
147         return -errno.ENOENT
148     def readdir(self, offset):
149         return -errno.ENOENT
150     def read(self, length, offset):
151         return -errno.ENOENT
152     def readlink(self):
153         return -errno.ENOENT
154     def write(self, length, offset):
155         return -errno.ENOENT
156
157 class TreeEntry(TreeKey):
158     default_mode = 0444
159     
160     def __new__(cls, contents, mode=None):
161         return super(TreeEntry, cls).__new__(cls, contents)
162     
163     def __init__(self, contents, mode=None):
164         if mode is None:
165             self.mode = self.default_mode
166         else:
167             self.mode = mode
168         
169         super(TreeEntry, self).__init__(contents)
170
171 class Directory(TreeEntry, list):
172     """
173     A dummy class representing a filesystem entry that should be a
174     directory
175     """
176     default_mode = 0555
177
178     def getattr(self):
179         st = RouteStat()
180         st.st_mode = stat.S_IFDIR | self.mode
181         st.st_nlink = 2
182         return st
183
184     def readdir(self, offset):
185         for member in ['.', '..'] + self:
186             yield fuse.Direntry(str(member))
187
188 class Symlink(TreeEntry, str):
189     """
190     A dummy class representing something that should be a symlink
191     """
192     default_mode = 0777
193
194     def getattr(self):
195         st = RouteStat()
196         st.st_mode = stat.S_IFLNK | self.mode
197         st.st_nlink = 1
198         st.st_size = len(self)
199         return st
200
201     def readlink(self):
202         return self
203
204 class File(TreeEntry, str):
205     """
206     A dummy class representing something that should be a file
207     """
208     default_mode = 0444
209
210     def getattr(self):
211         st = RouteStat()
212         st.st_mode = stat.S_IFREG | self.mode
213         st.st_nlink = 1
214         st.st_size = len(self)
215         return st
216
217     def read(self, length, offset):
218         return self[offset:offset + length]
219
220 def main(cls):
221     """
222     A convenience function for initializing a RouteFS filesystem
223     """
224     server = cls(version="%prog " + fuse.__version__,
225                  usage=fuse.Fuse.fusage,
226                  dash_s_do='setsingle')
227     server.parse(values=server, errex=1)
228     server.main()
229
230 from dictfs import DictFS
231
232 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']