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