Fix DictFS to use auto-conversion convention
[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
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         obj = self._get_file(path)
101         if type(obj) is not Directory:
102             return
103         else:
104             for member in ['.', '..'] + obj:
105                 yield fuse.Direntry(str(member))
106     
107     def getattr(self, path):
108         """
109         Return the stat information for a path
110         
111         The stat information for a directory, symlink, or file is
112         predetermined based on which it is.
113         """
114         obj = self._get_file(path)
115         if obj is None:
116             return -errno.ENOENT
117         
118         st = RouteStat()
119         if type(obj) is Directory:
120             st.st_mode = stat.S_IFDIR | obj.mode
121             st.st_nlink = 2
122         elif type(obj) is Symlink:
123             st.st_mode = stat.S_IFLNK | obj.mode
124             st.st_nlink = 1
125             st.st_size = len(obj)
126         else:
127             st.st_mode = stat.S_IFREG | obj.mode
128             st.st_nlink = 1
129             st.st_size = len(obj)
130         
131         return st
132     
133     def read(self, path, length, offset):
134         """
135         If the path specified is a file, return the requested portion
136         of the file
137         """
138         obj = self._get_file(path)
139         if obj is None:
140             return -errno.ENOENT
141         elif type(obj) in (Directory, Symlink):
142             return -errno.EINVAL
143         else:
144             return obj[offset:offset + length]
145     
146     def readlink(self, path):
147         """
148         If the path specified is a symlink, return the target
149         """
150         obj = self._get_file(path)
151         if type(obj) is not Symlink:
152             return -errno.EINVAL
153         else:
154             return obj
155
156 class TreeEntry(object):
157     default_mode = 0444
158     
159     def __new__(cls, contents, mode=None):
160         return super(TreeEntry, cls).__new__(cls, contents)
161     
162     def __init__(self, contents, mode=None):
163         if mode is None:
164             self.mode = self.default_mode
165         else:
166             self.mode = mode
167         
168         super(TreeEntry, self).__init__(contents)
169
170 class Directory(TreeEntry, list):
171     """
172     A dummy class representing a filesystem entry that should be a
173     directory
174     """
175     default_mode = 0555
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 class File(TreeEntry, str):
184     """
185     A dummy class representing something that should be a file
186     """
187     default_mode = 0444
188
189 def main(cls):
190     """
191     A convenience function for initializing a RouteFS filesystem
192     """
193     server = cls(version="%prog " + fuse.__version__,
194                  usage=fuse.Fuse.fusage,
195                  dash_s_do='setsingle')
196     server.parse(errex=1)
197     server.main()
198
199 from dictfs import DictFS
200
201 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']