Make read() 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         return self._get_file(path).read(length, offset)
117     
118     def readlink(self, path):
119         """
120         If the path specified is a symlink, return the target
121         """
122         obj = self._get_file(path)
123         if obj is None:
124             return -errno.ENOENT
125         elif type(obj) is not Symlink:
126             return -errno.EINVAL
127         else:
128             return obj
129
130 class TreeKey(object):
131     def getattr(self):
132         return -errno.EINVAL
133     def readdir(self, offset):
134         return -errno.EINVAL
135     def read(self, length, offset):
136         return -errno.EINVAL
137
138 class NoEntry(TreeKey):
139     def getattr(self):
140         return -errno.ENOENT
141     def readdir(self, offset):
142         return -errno.ENOENT
143     def read(self, length, offset):
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 class File(TreeEntry, str):
191     """
192     A dummy class representing something that should be a file
193     """
194     default_mode = 0444
195
196     def getattr(self):
197         st = RouteStat()
198         st.st_mode = stat.S_IFREG | self.mode
199         st.st_nlink = 1
200         st.st_size = len(self)
201         return st
202
203     def read(self, length, offset):
204         return self[offset:offset + length]
205
206 def main(cls):
207     """
208     A convenience function for initializing a RouteFS filesystem
209     """
210     server = cls(version="%prog " + fuse.__version__,
211                  usage=fuse.Fuse.fusage,
212                  dash_s_do='setsingle')
213     server.parse(values=server, errex=1)
214     server.main()
215
216 from dictfs import DictFS
217
218 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']