|
1 """File System Proxy. |
|
2 |
|
3 Provide an OS-neutral view on a file system, locally or remotely. |
|
4 The functionality is geared towards implementing some sort of |
|
5 rdist-like utility between a Mac and a UNIX system. |
|
6 |
|
7 The module defines three classes: |
|
8 |
|
9 FSProxyLocal -- used for local access |
|
10 FSProxyServer -- used on the server side of remote access |
|
11 FSProxyClient -- used on the client side of remote access |
|
12 |
|
13 The remote classes are instantiated with an IP address and an optional |
|
14 verbosity flag. |
|
15 """ |
|
16 |
|
17 import server |
|
18 import client |
|
19 import md5 |
|
20 import os |
|
21 import fnmatch |
|
22 from stat import * |
|
23 import time |
|
24 import fnmatch |
|
25 |
|
26 if os.name == 'mac': |
|
27 import macfs |
|
28 maxnamelen = 31 |
|
29 else: |
|
30 macfs = None |
|
31 maxnamelen = 255 |
|
32 |
|
33 skipnames = (os.curdir, os.pardir) |
|
34 |
|
35 |
|
36 class FSProxyLocal: |
|
37 |
|
38 def __init__(self): |
|
39 self._dirstack = [] |
|
40 self._ignore = ['*.pyc'] + self._readignore() |
|
41 |
|
42 def _close(self): |
|
43 while self._dirstack: |
|
44 self.back() |
|
45 |
|
46 def _readignore(self): |
|
47 file = self._hide('ignore') |
|
48 try: |
|
49 f = open(file) |
|
50 except IOError: |
|
51 file = self._hide('synctree.ignorefiles') |
|
52 try: |
|
53 f = open(file) |
|
54 except IOError: |
|
55 return [] |
|
56 ignore = [] |
|
57 while 1: |
|
58 line = f.readline() |
|
59 if not line: break |
|
60 if line[-1] == '\n': line = line[:-1] |
|
61 ignore.append(line) |
|
62 f.close() |
|
63 return ignore |
|
64 |
|
65 def _hidden(self, name): |
|
66 if os.name == 'mac': |
|
67 return name[0] == '(' and name[-1] == ')' |
|
68 else: |
|
69 return name[0] == '.' |
|
70 |
|
71 def _hide(self, name): |
|
72 if os.name == 'mac': |
|
73 return '(%s)' % name |
|
74 else: |
|
75 return '.%s' % name |
|
76 |
|
77 def visible(self, name): |
|
78 if len(name) > maxnamelen: return 0 |
|
79 if name[-1] == '~': return 0 |
|
80 if name in skipnames: return 0 |
|
81 if self._hidden(name): return 0 |
|
82 head, tail = os.path.split(name) |
|
83 if head or not tail: return 0 |
|
84 if macfs: |
|
85 if os.path.exists(name) and not os.path.isdir(name): |
|
86 try: |
|
87 fs = macfs.FSSpec(name) |
|
88 c, t = fs.GetCreatorType() |
|
89 if t != 'TEXT': return 0 |
|
90 except macfs.error, msg: |
|
91 print "***", name, msg |
|
92 return 0 |
|
93 else: |
|
94 if os.path.islink(name): return 0 |
|
95 if '\0' in open(name, 'rb').read(512): return 0 |
|
96 for ign in self._ignore: |
|
97 if fnmatch.fnmatch(name, ign): return 0 |
|
98 return 1 |
|
99 |
|
100 def check(self, name): |
|
101 if not self.visible(name): |
|
102 raise os.error, "protected name %s" % repr(name) |
|
103 |
|
104 def checkfile(self, name): |
|
105 self.check(name) |
|
106 if not os.path.isfile(name): |
|
107 raise os.error, "not a plain file %s" % repr(name) |
|
108 |
|
109 def pwd(self): |
|
110 return os.getcwd() |
|
111 |
|
112 def cd(self, name): |
|
113 self.check(name) |
|
114 save = os.getcwd(), self._ignore |
|
115 os.chdir(name) |
|
116 self._dirstack.append(save) |
|
117 self._ignore = self._ignore + self._readignore() |
|
118 |
|
119 def back(self): |
|
120 if not self._dirstack: |
|
121 raise os.error, "empty directory stack" |
|
122 dir, ignore = self._dirstack[-1] |
|
123 os.chdir(dir) |
|
124 del self._dirstack[-1] |
|
125 self._ignore = ignore |
|
126 |
|
127 def _filter(self, files, pat = None): |
|
128 if pat: |
|
129 def keep(name, pat = pat): |
|
130 return fnmatch.fnmatch(name, pat) |
|
131 files = filter(keep, files) |
|
132 files = filter(self.visible, files) |
|
133 files.sort() |
|
134 return files |
|
135 |
|
136 def list(self, pat = None): |
|
137 files = os.listdir(os.curdir) |
|
138 return self._filter(files, pat) |
|
139 |
|
140 def listfiles(self, pat = None): |
|
141 files = os.listdir(os.curdir) |
|
142 files = filter(os.path.isfile, files) |
|
143 return self._filter(files, pat) |
|
144 |
|
145 def listsubdirs(self, pat = None): |
|
146 files = os.listdir(os.curdir) |
|
147 files = filter(os.path.isdir, files) |
|
148 return self._filter(files, pat) |
|
149 |
|
150 def exists(self, name): |
|
151 return self.visible(name) and os.path.exists(name) |
|
152 |
|
153 def isdir(self, name): |
|
154 return self.visible(name) and os.path.isdir(name) |
|
155 |
|
156 def islink(self, name): |
|
157 return self.visible(name) and os.path.islink(name) |
|
158 |
|
159 def isfile(self, name): |
|
160 return self.visible(name) and os.path.isfile(name) |
|
161 |
|
162 def sum(self, name): |
|
163 self.checkfile(name) |
|
164 BUFFERSIZE = 1024*8 |
|
165 f = open(name) |
|
166 sum = md5.new() |
|
167 while 1: |
|
168 buffer = f.read(BUFFERSIZE) |
|
169 if not buffer: |
|
170 break |
|
171 sum.update(buffer) |
|
172 return sum.digest() |
|
173 |
|
174 def size(self, name): |
|
175 self.checkfile(name) |
|
176 return os.stat(name)[ST_SIZE] |
|
177 |
|
178 def mtime(self, name): |
|
179 self.checkfile(name) |
|
180 return time.localtime(os.stat(name)[ST_MTIME]) |
|
181 |
|
182 def stat(self, name): |
|
183 self.checkfile(name) |
|
184 size = os.stat(name)[ST_SIZE] |
|
185 mtime = time.localtime(os.stat(name)[ST_MTIME]) |
|
186 return size, mtime |
|
187 |
|
188 def info(self, name): |
|
189 sum = self.sum(name) |
|
190 size = os.stat(name)[ST_SIZE] |
|
191 mtime = time.localtime(os.stat(name)[ST_MTIME]) |
|
192 return sum, size, mtime |
|
193 |
|
194 def _list(self, function, list): |
|
195 if list is None: |
|
196 list = self.listfiles() |
|
197 res = [] |
|
198 for name in list: |
|
199 try: |
|
200 res.append((name, function(name))) |
|
201 except (os.error, IOError): |
|
202 res.append((name, None)) |
|
203 return res |
|
204 |
|
205 def sumlist(self, list = None): |
|
206 return self._list(self.sum, list) |
|
207 |
|
208 def statlist(self, list = None): |
|
209 return self._list(self.stat, list) |
|
210 |
|
211 def mtimelist(self, list = None): |
|
212 return self._list(self.mtime, list) |
|
213 |
|
214 def sizelist(self, list = None): |
|
215 return self._list(self.size, list) |
|
216 |
|
217 def infolist(self, list = None): |
|
218 return self._list(self.info, list) |
|
219 |
|
220 def _dict(self, function, list): |
|
221 if list is None: |
|
222 list = self.listfiles() |
|
223 dict = {} |
|
224 for name in list: |
|
225 try: |
|
226 dict[name] = function(name) |
|
227 except (os.error, IOError): |
|
228 pass |
|
229 return dict |
|
230 |
|
231 def sumdict(self, list = None): |
|
232 return self.dict(self.sum, list) |
|
233 |
|
234 def sizedict(self, list = None): |
|
235 return self.dict(self.size, list) |
|
236 |
|
237 def mtimedict(self, list = None): |
|
238 return self.dict(self.mtime, list) |
|
239 |
|
240 def statdict(self, list = None): |
|
241 return self.dict(self.stat, list) |
|
242 |
|
243 def infodict(self, list = None): |
|
244 return self._dict(self.info, list) |
|
245 |
|
246 def read(self, name, offset = 0, length = -1): |
|
247 self.checkfile(name) |
|
248 f = open(name) |
|
249 f.seek(offset) |
|
250 if length == 0: |
|
251 data = '' |
|
252 elif length < 0: |
|
253 data = f.read() |
|
254 else: |
|
255 data = f.read(length) |
|
256 f.close() |
|
257 return data |
|
258 |
|
259 def create(self, name): |
|
260 self.check(name) |
|
261 if os.path.exists(name): |
|
262 self.checkfile(name) |
|
263 bname = name + '~' |
|
264 try: |
|
265 os.unlink(bname) |
|
266 except os.error: |
|
267 pass |
|
268 os.rename(name, bname) |
|
269 f = open(name, 'w') |
|
270 f.close() |
|
271 |
|
272 def write(self, name, data, offset = 0): |
|
273 self.checkfile(name) |
|
274 f = open(name, 'r+') |
|
275 f.seek(offset) |
|
276 f.write(data) |
|
277 f.close() |
|
278 |
|
279 def mkdir(self, name): |
|
280 self.check(name) |
|
281 os.mkdir(name, 0777) |
|
282 |
|
283 def rmdir(self, name): |
|
284 self.check(name) |
|
285 os.rmdir(name) |
|
286 |
|
287 |
|
288 class FSProxyServer(FSProxyLocal, server.Server): |
|
289 |
|
290 def __init__(self, address, verbose = server.VERBOSE): |
|
291 FSProxyLocal.__init__(self) |
|
292 server.Server.__init__(self, address, verbose) |
|
293 |
|
294 def _close(self): |
|
295 server.Server._close(self) |
|
296 FSProxyLocal._close(self) |
|
297 |
|
298 def _serve(self): |
|
299 server.Server._serve(self) |
|
300 # Retreat into start directory |
|
301 while self._dirstack: self.back() |
|
302 |
|
303 |
|
304 class FSProxyClient(client.Client): |
|
305 |
|
306 def __init__(self, address, verbose = client.VERBOSE): |
|
307 client.Client.__init__(self, address, verbose) |
|
308 |
|
309 |
|
310 def test(): |
|
311 import string |
|
312 import sys |
|
313 if sys.argv[1:]: |
|
314 port = string.atoi(sys.argv[1]) |
|
315 else: |
|
316 port = 4127 |
|
317 proxy = FSProxyServer(('', port)) |
|
318 proxy._serverloop() |
|
319 |
|
320 |
|
321 if __name__ == '__main__': |
|
322 test() |