e0ff03f6b6988fb06e3414844b2da2e72d16cff9
[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 | 0755
117             st.st_nlink = 2
118         elif type(obj) is Symlink:
119             st.st_mode = stat.S_IFLNK | 0777
120             st.st_nlink = 1
121             st.st_size = len(obj)
122         else:
123             st.st_mode = stat.S_IFREG | 0444
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 Directory(list):
153     """
154     A dummy class representing a filesystem entry that should be a
155     directory
156     """
157     pass
158
159 class Symlink(str):
160     """
161     A dummy class representing something that should be a symlink
162     """
163     pass
164
165 def main(cls):
166     """
167     A convenience function for initializing a RouteFS filesystem
168     """
169     server = cls(version="%prog " + fuse.__version__,
170                  usage=fuse.Fuse.fusage,
171                  dash_s_do='setsingle')
172     server.parse(errex=1)
173     server.main()
174
175 from dictfs import DictFS
176
177 __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'main']