871774ed95af7c450ce0260cfd52415044b40540
[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 from dictfs import DictFS
19
20 fuse.fuse_python_api = (0, 2)
21
22 class RouteStat(fuse.Stat):
23     """
24     RouteStat is a descendent of fuse.Stat, defined to make sure that
25     all of the necessary attributes are always defined
26     """
27     def __init__(self):
28         self.st_mode = 0
29         self.st_ino = 0
30         self.st_dev = 0
31         self.st_nlink = 0
32         self.st_uid = 0
33         self.st_gid = 0
34         self.st_size = 0
35         self.st_atime = 0
36         self.st_mtime = 0
37         self.st_ctime = 0
38
39 class RouteMeta(type):
40     """
41     Metaclass to calculate controller methods
42     
43     Routes needs to be pre-seeded with a list of "controllers". For
44     all descendents of RouteFS, the list of controllers is defined to
45     be any non-private methods of the class that were not in the
46     RouteFS class.
47     """
48     def __init__(cls, classname, bases, dict_):
49         super(RouteMeta, cls).__init__(classname, bases, dict_)
50         if bases != (fuse.Fuse,):
51             new_funcs = set(dict_.keys()).difference(dir(RouteFS))
52             cls.controllers([func for func in new_funcs \
53                                  if not func.startswith('_')])
54
55 class RouteFS(fuse.Fuse):
56     """
57     RouteFS: Web 2.0 for filesystems
58     """
59     __metaclass__ = RouteMeta
60     def __init__(self, *args, **kwargs):
61         super(RouteFS, self).__init__(*args, **kwargs)
62         
63         self.map = self.make_map()
64         self.map.create_regs(self.controller_list)
65         
66     def make_map(self):
67         """
68         This method should be overridden by descendents of RouteFS to
69         define the routing for the filesystem
70         """
71         m = routes.Mapper()
72         
73         m.connect(':controller')
74         
75         return m
76     
77     @classmethod
78     def controllers(cls, lst):
79         cls.controller_list = lst
80     
81     def _get_file(self, path):
82         """
83         Find the filesystem entry object for a given path
84         """
85         match = self.map.match(path)
86         if match is None:
87             return
88         controller = match.pop('controller')
89         result = getattr(self, controller)(**match)
90         return result
91     
92     def readdir(self, path, offset):
93         """
94         If the path referred to is a directory, return the elements of
95         that diectory
96         """
97         obj = self._get_file(path)
98         if type(obj) is not Directory:
99             return
100         else:
101             for member in ['.', '..'] + obj:
102                 yield fuse.Direntry(str(member))
103     
104     def getattr(self, path):
105         """
106         Return the stat information for a path
107         
108         The stat information for a directory, symlink, or file is
109         predetermined based on which it is.
110         """
111         obj = self._get_file(path)
112         if obj is None:
113             return -errno.ENOENT
114         
115         st = RouteStat()
116         if type(obj) is Directory:
117             st.st_mode = stat.S_IFDIR | 0755
118             st.st_nlink = 2
119         elif type(obj) is Symlink:
120             st.st_mode = stat.S_IFLNK | 0777
121             st.st_nlink = 1
122             st.st_size = len(obj)
123         else:
124             st.st_mode = stat.S_IFREG | 0444
125             st.st_nlink = 1
126             st.st_size = len(obj)
127         
128         return st
129     
130     def read(self, path, length, offset):
131         """
132         If the path specified is a file, return the requested portion
133         of the file
134         """
135         obj = self._get_file(path)
136         if obj is None:
137             return -errno.ENOENT
138         elif type(obj) in (Directory, Symlink):
139             return -errno.EINVAL
140         else:
141             return obj[offset:offset + length]
142     
143     def readlink(self, path):
144         """
145         If the path specified is a symlink, return the target
146         """
147         obj = self._get_file(path)
148         if type(obj) is not Symlink:
149             return -errno.EINVAL
150         else:
151             return obj
152
153 class Directory(list):
154     """
155     A dummy class representing a filesystem entry that should be a
156     directory
157     """
158     pass
159
160 class Symlink(str):
161     """
162     A dummy class representing something that should be a symlink
163     """
164     pass
165
166 def main(cls):
167     """
168     A convenience function for initializing a RouteFS filesystem
169     """
170     server = cls(version="%prog " + fuse.__version__,
171                  usage=fuse.Fuse.fusage,
172                  dash_s_do='setsingle')
173     server.parse(errex=1)
174     server.main()
175
176 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'main']