1abbf9a6a851fc34eeaa73871631c7637a255f03
[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 type(result) is str:
90             result = File(result)
91         if type(result) is list:
92             result = Directory(result)
93         return result
94     
95     def readdir(self, path, offset):
96         """
97         If the path referred to is a directory, return the elements of
98         that diectory
99         """
100         obj = self._get_file(path)
101         if type(obj) is not Directory:
102             return
103         else:
104             for member in ['.', '..'] + obj:
105                 yield fuse.Direntry(str(member))
106     
107     def getattr(self, path):
108         """
109         Return the stat information for a path
110         
111         The stat information for a directory, symlink, or file is
112         predetermined based on which it is.
113         """
114         return self._get_file(path).getattr()
115     
116     def read(self, path, length, offset):
117         """
118         If the path specified is a file, return the requested portion
119         of the file
120         """
121         obj = self._get_file(path)
122         if type(obj) is NoEntry:
123             return -errno.ENOENT
124         elif type(obj) in (Directory, Symlink):
125             return -errno.EINVAL
126         else:
127             return obj[offset:offset + length]
128     
129     def readlink(self, path):
130         """
131         If the path specified is a symlink, return the target
132         """
133         obj = self._get_file(path)
134         if obj is None:
135             return -errno.ENOENT
136         elif type(obj) is not Symlink:
137             return -errno.EINVAL
138         else:
139             return obj
140
141 class TreeKey(object):
142     def getattr(self):
143         return -errno.EINVAL
144
145 class NoEntry(TreeKey):
146     def getattr(self):
147         return -errno.ENOENT
148
149 class TreeEntry(TreeKey):
150     default_mode = 0444
151     
152     def __new__(cls, contents, mode=None):
153         return super(TreeEntry, cls).__new__(cls, contents)
154     
155     def __init__(self, contents, mode=None):
156         if mode is None:
157             self.mode = self.default_mode
158         else:
159             self.mode = mode
160         
161         super(TreeEntry, self).__init__(contents)
162
163 class Directory(TreeEntry, list):
164     """
165     A dummy class representing a filesystem entry that should be a
166     directory
167     """
168     default_mode = 0555
169
170     def getattr(self):
171         st = RouteStat()
172         st.st_mode = stat.S_IFDIR | self.mode
173         st.st_nlink = 2
174         return st
175
176 class Symlink(TreeEntry, str):
177     """
178     A dummy class representing something that should be a symlink
179     """
180     default_mode = 0777
181
182     def getattr(self):
183         st = RouteStat()
184         st.st_mode = stat.S_IFLNK | self.mode
185         st.st_nlink = 1
186         st.st_size = len(self)
187         return st
188
189 class File(TreeEntry, str):
190     """
191     A dummy class representing something that should be a file
192     """
193     default_mode = 0444
194
195     def getattr(self):
196         st = RouteStat()
197         st.st_mode = stat.S_IFREG | self.mode
198         st.st_nlink = 1
199         st.st_size = len(self)
200         return st
201
202 def main(cls):
203     """
204     A convenience function for initializing a RouteFS filesystem
205     """
206     server = cls(version="%prog " + fuse.__version__,
207                  usage=fuse.Fuse.fusage,
208                  dash_s_do='setsingle')
209     server.parse(values=server, errex=1)
210     server.main()
211
212 from dictfs import DictFS
213
214 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']