294ec930620e4e68fee548f60fc8cc0bbc25d0ba
[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     Any method that will be used as the controller in a Routes mapping
44     (either by explicitly specifying the controller or by using the
45     ':controller' variable) must be added to RouteFS.controllers
46     """
47     controllers = []
48     def __init__(self, *args, **kwargs):
49         super(RouteFS, self).__init__(*args, **kwargs)
50
51         self.map = self.make_map()
52         self.map.create_regs(self.controllers)
53
54     def make_map(self):
55         """
56         This method should be overridden by descendents of RouteFS to
57         define the routing for the filesystem
58         """
59         m = routes.Mapper()
60
61         m.connect(':controller')
62
63         return m
64
65     def _get_file(self, path):
66         """
67         Find the filesystem entry object for a given path
68         """
69         match = self.map.match(path)
70         if match is None:
71             return NoEntry()
72         controller = match.pop('controller')
73         result = getattr(self, controller)(**match)
74         if result is None:
75             return NoEntry()
76         if type(result) is str:
77             result = File(result)
78         if type(result) is list:
79             result = Directory(result)
80         return result
81
82     def readdir(self, path, offset):
83         """
84         If the path referred to is a directory, return the elements of
85         that diectory
86         """
87         return self._get_file(path).readdir(offset)
88
89     def getattr(self, path):
90         """
91         Return the stat information for a path
92
93         The stat information for a directory, symlink, or file is
94         predetermined based on which it is.
95         """
96         return self._get_file(path).getattr()
97
98     def read(self, path, length, offset):
99         """
100         If the path specified is a file, return the requested portion
101         of the file
102         """
103         return self._get_file(path).read(length, offset)
104
105     def readlink(self, path):
106         """
107         If the path specified is a symlink, return the target
108         """
109         return self._get_file(path).readlink()
110
111     def write(self, path, buf, offset):
112         """
113         If the path specified is a file, call the appropriate member 
114         on the file
115         """
116         return self._get_file(path).write(buf, offset)
117
118
119 class TreeKey(object):
120     def getattr(self):
121         return -errno.EINVAL
122     def readdir(self, offset):
123         return -errno.EINVAL
124     def read(self, length, offset):
125         return -errno.EINVAL
126     def readlink(self):
127         return -errno.EINVAL
128     def write(self, length, offset):
129         return -errno.EINVAL
130
131
132 class NoEntry(TreeKey):
133     def getattr(self):
134         return -errno.ENOENT
135     def readdir(self, offset):
136         return -errno.ENOENT
137     def read(self, length, offset):
138         return -errno.ENOENT
139     def readlink(self):
140         return -errno.ENOENT
141     def write(self, length, offset):
142         return -errno.ENOENT
143
144
145 class TreeEntry(TreeKey):
146     default_mode = 0444
147
148     def __new__(cls, contents, mode=None):
149         return super(TreeEntry, cls).__new__(cls, contents)
150
151     def __init__(self, contents, mode=None):
152         if mode is None:
153             self.mode = self.default_mode
154         else:
155             self.mode = mode
156
157         super(TreeEntry, self).__init__(contents)
158
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
178 class Symlink(TreeEntry, str):
179     """
180     A dummy class representing something that should be a symlink
181     """
182     default_mode = 0777
183
184     def getattr(self):
185         st = RouteStat()
186         st.st_mode = stat.S_IFLNK | self.mode
187         st.st_nlink = 1
188         st.st_size = len(self)
189         return st
190
191     def readlink(self):
192         return self
193
194
195 class File(TreeEntry, str):
196     """
197     A dummy class representing something that should be a file
198     """
199     default_mode = 0444
200
201     def getattr(self):
202         st = RouteStat()
203         st.st_mode = stat.S_IFREG | self.mode
204         st.st_nlink = 1
205         st.st_size = len(self)
206         return st
207
208     def read(self, length, offset):
209         return self[offset:offset + length]
210
211
212 def main(cls):
213     """
214     A convenience function for initializing a RouteFS filesystem
215     """
216     server = cls(version="%prog " + fuse.__version__,
217                  usage=fuse.Fuse.fusage,
218                  dash_s_do='setsingle')
219     server.parse(values=server, errex=1)
220     server.main()
221
222
223 from dictfs import DictFS
224
225 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']