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