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