8d620e67aa7fbec21b697629c8740b703911c6ef
[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 import fuse
11 import routes
12 import errno
13 import stat
14
15 fuse.fuse_python_api = (0, 2)
16
17 class RouteStat(fuse.Stat):
18     """
19     RouteStat is a descendent of fuse.Stat, defined to make sure that
20     all of the necessary attributes are always defined
21     """
22     def __init__(self):
23         self.st_mode = 0
24         self.st_ino = 0
25         self.st_dev = 0
26         self.st_nlink = 0
27         self.st_uid = 0
28         self.st_gid = 0
29         self.st_size = 0
30         self.st_atime = 0
31         self.st_mtime = 0
32         self.st_ctime = 0
33
34 class RouteFS(fuse.Fuse):
35     """
36     RouteFS: Web 2.0 for filesystems
37     
38     Any method that will be used as the controller in a Routes mapping
39     (either by explicitly specifying the controller or by using the
40     ':controller' variable) must be added to RouteFS.controllers
41     """
42     controllers = []
43     def __init__(self, *args, **kwargs):
44         super(RouteFS, self).__init__(*args, **kwargs)
45         
46         self.map.create_regs(self.controllers)
47     
48     @property
49     def map(self):
50         """
51         This property should be overridden by descendents of RouteFS
52         to define the routing for the filesystem
53         """
54         m = routes.Mapper()
55         
56         m.connect(':controller')
57         
58         return m
59     
60     def _get_file(self, path):
61         """
62         Find the filesystem entry object for a given path
63         """
64         match = self.map.match(path)
65         if match is None:
66             return NoEntry()
67         controller = match.pop('controller')
68         result = getattr(self, controller)(**match)
69         if result is None:
70             return NoEntry()
71         if type(result) is str:
72             result = File(result)
73         if type(result) is list:
74             result = Directory(result)
75         return result
76     
77     def readdir(self, path, offset):
78         """
79         If the path referred to is a directory, return the elements of
80         that diectory
81         """
82         return self._get_file(path).readdir(offset)
83     
84     def getattr(self, path):
85         """
86         Return the stat information for a path
87         
88         The stat information for a directory, symlink, or file is
89         predetermined based on which it is.
90         """
91         return self._get_file(path).getattr()
92     
93     def read(self, path, length, offset):
94         """
95         If the path specified is a file, return the requested portion
96         of the file
97         """
98         return self._get_file(path).read(length, offset)
99     
100     def readlink(self, path):
101         """
102         If the path specified is a symlink, return the target
103         """
104         return self._get_file(path).readlink()
105     
106     def write(self, path, buf, offset):
107         """
108         If the path specified is a file, call the appropriate member 
109         on the file
110         """
111         return self._get_file(path).write(buf, offset)
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 class NoEntry(TreeKey):
126     def getattr(self):
127         return -errno.ENOENT
128     def readdir(self, offset):
129         return -errno.ENOENT
130     def read(self, length, offset):
131         return -errno.ENOENT
132     def readlink(self):
133         return -errno.ENOENT
134     def write(self, length, offset):
135         return -errno.ENOENT
136
137 class TreeEntry(TreeKey):
138     default_mode = 0444
139     
140     def __new__(cls, contents, mode=None):
141         return super(TreeEntry, cls).__new__(cls, contents)
142     
143     def __init__(self, contents, mode=None):
144         if mode is None:
145             self.mode = self.default_mode
146         else:
147             self.mode = mode
148         
149         super(TreeEntry, self).__init__(contents)
150
151 class Directory(TreeEntry, list):
152     """
153     A dummy class representing a filesystem entry that should be a
154     directory
155     """
156     default_mode = 0555
157
158     def getattr(self):
159         st = RouteStat()
160         st.st_mode = stat.S_IFDIR | self.mode
161         st.st_nlink = 2
162         return st
163
164     def readdir(self, offset):
165         for member in ['.', '..'] + self:
166             yield fuse.Direntry(str(member))
167
168 class Symlink(TreeEntry, str):
169     """
170     A dummy class representing something that should be a symlink
171     """
172     default_mode = 0777
173
174     def getattr(self):
175         st = RouteStat()
176         st.st_mode = stat.S_IFLNK | self.mode
177         st.st_nlink = 1
178         st.st_size = len(self)
179         return st
180
181     def readlink(self):
182         return self
183
184 class File(TreeEntry, str):
185     """
186     A dummy class representing something that should be a file
187     """
188     default_mode = 0444
189
190     def getattr(self):
191         st = RouteStat()
192         st.st_mode = stat.S_IFREG | self.mode
193         st.st_nlink = 1
194         st.st_size = len(self)
195         return st
196
197     def read(self, length, offset):
198         return self[offset:offset + length]
199
200 def main(cls):
201     """
202     A convenience function for initializing a RouteFS filesystem
203     """
204     server = cls(version="%prog " + fuse.__version__,
205                  usage=fuse.Fuse.fusage,
206                  dash_s_do='setsingle')
207     server.parse(values=server, errex=1)
208     server.main()
209
210 from dictfs import DictFS
211
212 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']