Return ENOENT if a file doesn't exist for readlink.
[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 obj is None:
152             return -errno.ENOENT
153         elif type(obj) is not Symlink:
154             return -errno.EINVAL
155         else:
156             return obj
157
158 class TreeEntry(object):
159     default_mode = 0444
160     
161     def __new__(cls, contents, mode=None):
162         return super(TreeEntry, cls).__new__(cls, contents)
163     
164     def __init__(self, contents, mode=None):
165         if mode is None:
166             self.mode = self.default_mode
167         else:
168             self.mode = mode
169         
170         super(TreeEntry, self).__init__(contents)
171
172 class Directory(TreeEntry, list):
173     """
174     A dummy class representing a filesystem entry that should be a
175     directory
176     """
177     default_mode = 0555
178
179 class Symlink(TreeEntry, str):
180     """
181     A dummy class representing something that should be a symlink
182     """
183     default_mode = 0777
184
185 class File(TreeEntry, str):
186     """
187     A dummy class representing something that should be a file
188     """
189     default_mode = 0444
190
191 def main(cls):
192     """
193     A convenience function for initializing a RouteFS filesystem
194     """
195     server = cls(version="%prog " + fuse.__version__,
196                  usage=fuse.Fuse.fusage,
197                  dash_s_do='setsingle')
198     server.parse(values=server, errex=1)
199     server.main()
200
201 from dictfs import DictFS
202
203 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']