FTP aware file object
Andy Jewell
andy at wild-flower.co.uk
Fri May 30 17:31:22 EDT 2003
Just after some *constructive* criticism on a little class I've written, based
on the builtin 'file' class.
Firstly: what it does.
It's a wrapper that fakes the ability to open of files over ftp, and treat
them as if they were just bog ordinary files. It has a 'file' compatable
interface, but it notices when you pass it an ftp url, and will download the
remote file before opening a local copy (rather like urllib). The
difference, though, is that If the file is opened for any kind of writing
operation, it is uploaded back to the server when you close it.
Secondly: why.
I'm trying to fully grok new style classes, and I wanted to have a way to make
other programs I've written ftp-aware without having to rewrite them. Oh,
and maybe others (idle for instance).
I've read Guido's essay on new-style classes, and I think I've applied what
I've learned with this little monstrosity, but, in particular with super()
calls, I wanted some sort of confirmation that i was doing it 'right'.
Another question is about my 'repatching' the close() method to refer to
super(ftpfile,self).close when operating on solely local files... is this the
best way, or should I just supercall it within my own close meth? I had some
vague notion about better performance doing it this way ;-) - am I right?
Lastly, is it feasable to rebind 'file' and 'open' globally, or do I have to
do this in each and every module that I want to use this functionality in?
What I mean, is, within the toplevel module of a program, can I just do:
import ftpfile
file=ftpfile.ftpfile
open=ftpfile.ftpfile
?
Note that this is still seriously 'development' code - I haven't tested every
single aspect, yet, so it's *bound* to have errors - oh, and it still prints
debugging info in various places, and there are some glaringly stupidly
hard-coded bits; plain read and write operations *do* seem to work correctly,
though.
Go on, have a laugh... you know you want to :-p
-andyj
------8<------------------8<------------------8<------------------8<------------------
import ftplib
import os
localfilecache="c:/temp/ftpcache/"
class ftpfile(file):
""" url,mode="r",buffering=-1
FTP aware file object which delegates "normal" file operations
to the superclass.
If the file name begins "ftp://", FTP mode is enabled.
In FTP mode, where reading of the file is required, the file is
first downloaded from the remote host, and where writing is
required, the file is uploaded when it is closed.
NOTE THAT FTP READS AND WRITES ARE PERFORMED ON A LOCAL COPY
"""
def __init__(self,name,mode="r",buffering=-1):
if not name.startswith("ftp://"):
return super(ftpfile).__init__(name,mode,buffering)
self.filemode=mode
self.decodeurl(name)
self.localfilename=os.path.join(localfilecache,
self.host,self.user,
self.path,self.filename)
localpath=os.path.join(localfilecache,
self.host,self.user,
self.path)
if not os.path.exists(localpath):
os.makedirs(localpath)
# Decide if we need to download the file first
if self.filemode[0] not in ["w","wb"]:
self.downloadfile(name,self.filemode)
# Short-circuit our close() method if file doesn't need
# to be uploaded
if self.filemode in ["r","rb"]:
self.close=super(ftpfile,self).close
#self.localfilehandle=file(self.localfilename,self.filemode)
return super(ftpfile,self).__init__(self.localfilename,mode,buffering)
def close(self):
# ... then upload it
ftpconn=ftplib.FTP(self.host,self.user,self.passwd,None)
ftpconn.cwd(self.path)
try:
ftpconn.delete(self.filename)
except:
pass
super(ftpfile,self).close()
self.localfilehandle=file(self.localfilename,"r")
if "b" in self.mode:
ftpconn.storbinary("STOR "+self.filename,self.localfilehandle)
else:
ftpconn.storlines("STOR "+self.filename,self.localfilehandle)
ftpconn.close()
# Close the file...
self.localfilehandle.close()
def downloadfile(self,name,mode):
# Use correct file mode
if "b" in mode:
retr=ftplib.FTP.retrbinary
self.writer=self.binwriter
localmode="wb"
else:
retr=ftplib.FTP.retrlines
self.writer=self.txtwriter
localmode="w"
# Download the file
if os.path.exists(self.localfilename):
os.remove(self.localfilename)
self.localfilehandle=file(self.localfilename,localmode)
ftpconn=ftplib.FTP(self.host,self.user,self.passwd)
ftpconn.cwd(self.path)
if "b" in mode:
ftpconn.retrbinary("RETR "+self.filename,self.binwriter)
else:
ftpconn.retrlines("RETR "+self.filename,self.txtwriter)
ftpconn.close()
self.localfilehandle.close()
def decodeurl(self,url):
""" decodes an ftp url into its component fields:
ftp://[user[:passwd]@]host[:port]/path/filename
/ / / / / /
| | | | | lastslash
| | | | firstslash
| | at portcolon
| colon
6
"""
# Sanity check:
if not url.startswith("ftp://"):
raise "Malformed url:",url
# Do we have a user:passwd pair?
at=url.find("@",6)
colon=url.find(":",6)
portcolon=url.find(":",at)
if at == -1:
at=6
self.user=None
self.passwd=None
else:
if colon == -1:
colon=at
self.user=url[6:colon]
self.passwd=url[colon+1:at]
if self.passwd == "":
self.passwd=None
firstslash=url.find("/",at)
if firstslash == -1:
raise "Malformed url:",url
if portcolon !=-1:
self.host=url[at+1:portcolon]
self.port=url[portcolon:firstslash]
else:
self.host=url[at+1:firstslash]
self.port=":21"
lastslash=url.rfind("/")
if lastslash == -1:
raise "Malformed url:",url
self.filename=url[lastslash+1:]
self.path=url[firstslash:lastslash]
print self.user+":"+self.passwd+"."
print self.host,self.path,self.filename
def binwriter(self,block):
self.localfilehandle.write(block)
def txtwriter(self,line):
self.localfilehandle.write(line+"\n")
def write(self,string):
print "Writing",repr(string)
super(ftpfile,self).write(string)
More information about the Python-list
mailing list