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