Eliminate the "controllers" attribute from RouteFS classes.
[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
10
11 import errno
12 import stat
13
14 import fuse
15 import routes
16
17
18 fuse.fuse_python_api = (0, 2)
19
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
39 class RouteFS(fuse.Fuse):
40     """
41     RouteFS: Web 2.0 for filesystems
42     """
43     def __init__(self, *args, **kwargs):
44         super(RouteFS, self).__init__(*args, **kwargs)
45
46         self.map = self.make_map()
47
48     def make_map(self):
49         """
50         This method should be overridden by descendents of RouteFS to
51         define the routing for the filesystem
52         """
53         m = routes.Mapper()
54
55         m.connect('{controller}')
56
57         return m
58
59     def _get_file(self, path):
60         """
61         Find the filesystem entry object for a given path
62         """
63         match = self.map.match(path)
64         if match is None:
65             return NoEntry()
66         controller = match.pop('controller')
67         result = getattr(self, controller)(**match)
68         if result is None:
69             return NoEntry()
70         if type(result) is str:
71             result = File(result)
72         if type(result) is list:
73             result = Directory(result)
74         return result
75
76     def readdir(self, path, offset):
77         """
78         If the path referred to is a directory, return the elements of
79         that diectory
80         """
81         return self._get_file(path).readdir(offset)
82
83     def getattr(self, path):
84         """
85         Return the stat information for a path
86
87         The stat information for a directory, symlink, or file is
88         predetermined based on which it is.
89         """
90         return self._get_file(path).getattr()
91
92     def read(self, path, length, offset):
93         """
94         If the path specified is a file, return the requested portion
95         of the file
96         """
97         return self._get_file(path).read(length, offset)
98
99     def readlink(self, path):
100         """
101         If the path specified is a symlink, return the target
102         """
103         return self._get_file(path).readlink()
104
105     def write(self, path, buf, offset):
106         """
107         If the path specified is a file, call the appropriate member 
108         on the file
109         """
110         return self._get_file(path).write(buf, offset)
111
112
113 class TreeKey(object):
114     def getattr(self):
115         return -errno.EINVAL
116     def readdir(self, offset):
117         return -errno.EINVAL
118     def read(self, length, offset):
119         return -errno.EINVAL
120     def readlink(self):
121         return -errno.EINVAL
122     def write(self, length, offset):
123         return -errno.EINVAL
124
125
126 class NoEntry(TreeKey):
127     def getattr(self):
128         return -errno.ENOENT
129     def readdir(self, offset):
130         return -errno.ENOENT
131     def read(self, length, offset):
132         return -errno.ENOENT
133     def readlink(self):
134         return -errno.ENOENT
135     def write(self, length, offset):
136         return -errno.ENOENT
137
138
139 class TreeEntry(TreeKey):
140     default_mode = 0444
141
142     def __new__(cls, contents, mode=None):
143         return super(TreeEntry, cls).__new__(cls, contents)
144
145     def __init__(self, contents, mode=None):
146         if mode is None:
147             self.mode = self.default_mode
148         else:
149             self.mode = mode
150
151         super(TreeEntry, self).__init__(contents)
152
153
154 class Directory(TreeEntry, list):
155     """
156     A dummy class representing a filesystem entry that should be a
157     directory
158     """
159     default_mode = 0555
160
161     def getattr(self):
162         st = RouteStat()
163         st.st_mode = stat.S_IFDIR | self.mode
164         st.st_nlink = 2
165         return st
166
167     def readdir(self, offset):
168         for member in ['.', '..'] + self:
169             yield fuse.Direntry(str(member))
170
171
172 class Symlink(TreeEntry, str):
173     """
174     A dummy class representing something that should be a symlink
175     """
176     default_mode = 0777
177
178     def getattr(self):
179         st = RouteStat()
180         st.st_mode = stat.S_IFLNK | self.mode
181         st.st_nlink = 1
182         st.st_size = len(self)
183         return st
184
185     def readlink(self):
186         return self
187
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 read(self, length, offset):
203         return self[offset:offset + length]
204
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
217 from dictfs import DictFS
218
219 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']