commit -- why not
diff --git a/Demo/pdist/FSProxy.py b/Demo/pdist/FSProxy.py
new file mode 100755
index 0000000..7510d1e
--- /dev/null
+++ b/Demo/pdist/FSProxy.py
@@ -0,0 +1,322 @@
+"""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, 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()