Update debian/changelog for 1.0.1-1
[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 class TreeKey(object):
127     def getattr(self):
128         return -errno.EINVAL
129     def readdir(self, offset):
130         return -errno.EINVAL
131     def read(self, length, offset):
132         return -errno.EINVAL
133     def readlink(self):
134         return -errno.EINVAL
135
136 class NoEntry(TreeKey):
137     def getattr(self):
138         return -errno.ENOENT
139     def readdir(self, offset):
140         return -errno.ENOENT
141     def read(self, length, offset):
142         return -errno.ENOENT
143     def readlink(self):
144         return -errno.ENOENT
145
146 class TreeEntry(TreeKey):
147     default_mode = 0444
148     
149     def __new__(cls, contents, mode=None):
150         return super(TreeEntry, cls).__new__(cls, contents)
151     
152     def __init__(self, contents, mode=None):
153         if mode is None:
154             self.mode = self.default_mode
155         else:
156             self.mode = mode
157         
158         super(TreeEntry, self).__init__(contents)
159
160 class Directory(TreeEntry, list):
161     """
162     A dummy class representing a filesystem entry that should be a
163     directory
164     """
165     default_mode = 0555
166
167     def getattr(self):
168         st = RouteStat()
169         st.st_mode = stat.S_IFDIR | self.mode
170         st.st_nlink = 2
171         return st
172
173     def readdir(self, offset):
174         for member in ['.', '..'] + self:
175             yield fuse.Direntry(str(member))
176
177 class Symlink(TreeEntry, str):
178     """
179     A dummy class representing something that should be a symlink
180     """
181     default_mode = 0777
182
183     def getattr(self):
184         st = RouteStat()
185         st.st_mode = stat.S_IFLNK | self.mode
186         st.st_nlink = 1
187         st.st_size = len(self)
188         return st
189
190     def readlink(self):
191         return self
192
193 class File(TreeEntry, str):
194     """
195     A dummy class representing something that should be a file
196     """
197     default_mode = 0444
198
199     def getattr(self):
200         st = RouteStat()
201         st.st_mode = stat.S_IFREG | self.mode
202         st.st_nlink = 1
203         st.st_size = len(self)
204         return st
205
206     def read(self, length, offset):
207         return self[offset:offset + length]
208
209 def main(cls):
210     """
211     A convenience function for initializing a RouteFS filesystem
212     """
213     server = cls(version="%prog " + fuse.__version__,
214                  usage=fuse.Fuse.fusage,
215                  dash_s_do='setsingle')
216     server.parse(values=server, errex=1)
217     server.main()
218
219 from dictfs import DictFS
220
221 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']