| """File System Proxy. |
| |
| Provide an OS-neutral view on a file system, locally or remotely. |
| The functionality is geared towards implementing some sort of |
| rdist-like utility between a Mac and a UNIX system. |
| |
| The module defines three classes: |
| |
| FSProxyLocal -- used for local access |
| FSProxyServer -- used on the server side of remote access |
| FSProxyClient -- used on the client side of remote access |
| |
| The remote classes are instantiated with an IP address and an optional |
| verbosity flag. |
| """ |
| |
| import server |
| import client |
| import md5 |
| import os |
| import fnmatch |
| from stat import * |
| import time |
| import fnmatch |
| |
| if os.name == 'mac': |
| import macfs |
| maxnamelen = 31 |
| else: |
| macfs = None |
| maxnamelen = 255 |
| |
| skipnames = (os.curdir, os.pardir) |
| |
| |
| class FSProxyLocal: |
| |
| def __init__(self): |
| self._dirstack = [] |
| self._ignore = ['*.pyc'] + self._readignore() |
| |
| def _close(self): |
| while self._dirstack: |
| self.back() |
| |
| def _readignore(self): |
| file = self._hide('ignore') |
| try: |
| f = open(file) |
| except IOError: |
| file = self._hide('synctree.ignorefiles') |
| try: |
| f = open(file) |
| except IOError: |
| return [] |
| ignore = [] |
| while 1: |
| line = f.readline() |
| if not line: break |
| if line[-1] == '\n': line = line[:-1] |
| ignore.append(line) |
| f.close() |
| return ignore |
| |
| def _hidden(self, name): |
| if os.name == 'mac': |
| return name[0] == '(' and name[-1] == ')' |
| else: |
| return name[0] == '.' |
| |
| def _hide(self, name): |
| if os.name == 'mac': |
| return '(%s)' % name |
| else: |
| return '.%s' % name |
| |
| def visible(self, name): |
| if len(name) > maxnamelen: return 0 |
| if name[-1] == '~': return 0 |
| if name in skipnames: return 0 |
| if self._hidden(name): return 0 |
| head, tail = os.path.split(name) |
| if head or not tail: return 0 |
| if macfs: |
| if os.path.exists(name) and not os.path.isdir(name): |
| try: |
| fs = macfs.FSSpec(name) |
| c, t = fs.GetCreatorType() |
| if t != 'TEXT': return 0 |
| except macfs.error as msg: |
| print "***", name, msg |
| return 0 |
| else: |
| if os.path.islink(name): return 0 |
| if '\0' in open(name, 'rb').read(512): return 0 |
| for ign in self._ignore: |
| if fnmatch.fnmatch(name, ign): return 0 |
| return 1 |
| |
| def check(self, name): |
| if not self.visible(name): |
| raise os.error, "protected name %s" % repr(name) |
| |
| def checkfile(self, name): |
| self.check(name) |
| if not os.path.isfile(name): |
| raise os.error, "not a plain file %s" % repr(name) |
| |
| def pwd(self): |
| return os.getcwd() |
| |
| def cd(self, name): |
| self.check(name) |
| save = os.getcwd(), self._ignore |
| os.chdir(name) |
| self._dirstack.append(save) |
| self._ignore = self._ignore + self._readignore() |
| |
| def back(self): |
| if not self._dirstack: |
| raise os.error, "empty directory stack" |
| dir, ignore = self._dirstack[-1] |
| os.chdir(dir) |
| del self._dirstack[-1] |
| self._ignore = ignore |
| |
| def _filter(self, files, pat = None): |
| if pat: |
| def keep(name, pat = pat): |
| return fnmatch.fnmatch(name, pat) |
| files = filter(keep, files) |
| files = filter(self.visible, files) |
| files.sort() |
| return files |
| |
| def list(self, pat = None): |
| files = os.listdir(os.curdir) |
| return self._filter(files, pat) |
| |
| def listfiles(self, pat = None): |
| files = os.listdir(os.curdir) |
| files = filter(os.path.isfile, files) |
| return self._filter(files, pat) |
| |
| def listsubdirs(self, pat = None): |
| files = os.listdir(os.curdir) |
| files = filter(os.path.isdir, files) |
| return self._filter(files, pat) |
| |
| def exists(self, name): |
| return self.visible(name) and os.path.exists(name) |
| |
| def isdir(self, name): |
| return self.visible(name) and os.path.isdir(name) |
| |
| def islink(self, name): |
| return self.visible(name) and os.path.islink(name) |
| |
| def isfile(self, name): |
| return self.visible(name) and os.path.isfile(name) |
| |
| def sum(self, name): |
| self.checkfile(name) |
| BUFFERSIZE = 1024*8 |
| f = open(name) |
| sum = md5.new() |
| while 1: |
| buffer = f.read(BUFFERSIZE) |
| if not buffer: |
| break |
| sum.update(buffer) |
| return sum.digest() |
| |
| def size(self, name): |
| self.checkfile(name) |
| return os.stat(name)[ST_SIZE] |
| |
| def mtime(self, name): |
| self.checkfile(name) |
| return time.localtime(os.stat(name)[ST_MTIME]) |
| |
| def stat(self, name): |
| self.checkfile(name) |
| size = os.stat(name)[ST_SIZE] |
| mtime = time.localtime(os.stat(name)[ST_MTIME]) |
| return size, mtime |
| |
| def info(self, name): |
| sum = self.sum(name) |
| size = os.stat(name)[ST_SIZE] |
| mtime = time.localtime(os.stat(name)[ST_MTIME]) |
| return sum, size, mtime |
| |
| def _list(self, function, list): |
| if list is None: |
| list = self.listfiles() |
| res = [] |
| for name in list: |
| try: |
| res.append((name, function(name))) |
| except (os.error, IOError): |
| res.append((name, None)) |
| return res |
| |
| def sumlist(self, list = None): |
| return self._list(self.sum, list) |
| |
| def statlist(self, list = None): |
| return self._list(self.stat, list) |
| |
| def mtimelist(self, list = None): |
| return self._list(self.mtime, list) |
| |
| def sizelist(self, list = None): |
| return self._list(self.size, list) |
| |
| def infolist(self, list = None): |
| return self._list(self.info, list) |
| |
| def _dict(self, function, list): |
| if list is None: |
| list = self.listfiles() |
| dict = {} |
| for name in list: |
| try: |
| dict[name] = function(name) |
| except (os.error, IOError): |
| pass |
| return dict |
| |
| def sumdict(self, list = None): |
| return self.dict(self.sum, list) |
| |
| def sizedict(self, list = None): |
| return self.dict(self.size, list) |
| |
| def mtimedict(self, list = None): |
| return self.dict(self.mtime, list) |
| |
| def statdict(self, list = None): |
| return self.dict(self.stat, list) |
| |
| def infodict(self, list = None): |
| return self._dict(self.info, list) |
| |
| def read(self, name, offset = 0, length = -1): |
| self.checkfile(name) |
| f = open(name) |
| f.seek(offset) |
| if length == 0: |
| data = '' |
| elif length < 0: |
| data = f.read() |
| else: |
| data = f.read(length) |
| f.close() |
| return data |
| |
| def create(self, name): |
| self.check(name) |
| if os.path.exists(name): |
| self.checkfile(name) |
| bname = name + '~' |
| try: |
| os.unlink(bname) |
| except os.error: |
| pass |
| os.rename(name, bname) |
| f = open(name, 'w') |
| f.close() |
| |
| def write(self, name, data, offset = 0): |
| self.checkfile(name) |
| f = open(name, 'r+') |
| f.seek(offset) |
| f.write(data) |
| f.close() |
| |
| def mkdir(self, name): |
| self.check(name) |
| os.mkdir(name, 0777) |
| |
| def rmdir(self, name): |
| self.check(name) |
| os.rmdir(name) |
| |
| |
| class FSProxyServer(FSProxyLocal, server.Server): |
| |
| def __init__(self, address, verbose = server.VERBOSE): |
| FSProxyLocal.__init__(self) |
| server.Server.__init__(self, address, verbose) |
| |
| def _close(self): |
| server.Server._close(self) |
| FSProxyLocal._close(self) |
| |
| def _serve(self): |
| server.Server._serve(self) |
| # Retreat into start directory |
| while self._dirstack: self.back() |
| |
| |
| class FSProxyClient(client.Client): |
| |
| def __init__(self, address, verbose = client.VERBOSE): |
| client.Client.__init__(self, address, verbose) |
| |
| |
| def test(): |
| import string |
| import sys |
| if sys.argv[1:]: |
| port = string.atoi(sys.argv[1]) |
| else: |
| port = 4127 |
| proxy = FSProxyServer(('', port)) |
| proxy._serverloop() |
| |
| |
| if __name__ == '__main__': |
| test() |