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