I use Net::FTP to access IIS on WinNT quite a bit at work.  When I have a 
problem, I debug it by trying to do what perl was doing using the Windows 
command line ftp client.  In your case, login, cd '/wholesite' and if that 
doesn't work, it is a problem with the way the IIS server is configured.

As for files showing up, but directories not showing up, I'm guessing you 
will find the same behavior from the command line ftp client.

As a side note, by default you can only view the ftp directory (e.g 
c:\Inetpub\wwwroot).  To make it more unix-like, you can add Virtual 
directories in the IIS Admin that allow you to traverse the whole file 
system.  For example, add /C: pointing to C:\ and now you can access 
/C:/Winnt, etc...   There's probably a good reason that this is not on by 
default - I guess Windows doesn't have a very good (or easy to administer) 
multi-user security architecture.

If anyone is interested, my first python project was an ftp script.  It 
takes an xml config file, are mirrors/copies/deletes directories and files.  
It can do search and replace on the files during the ftp.  I pasted it 
below.

Perl is great, but it good not to HAVE to use it all the time.  (I'm 
learning perl's cousin, Ruby, as well).

Jon


#!/usr/bin/python

"""
Usage: xmlftp.py --user FTPUSER --pass FTPPASS --servers 
SERVER1[,SERVER2],...] --xmlfile XMLFILE

takes a valid xml file that explains
what files and directories should be copied from
the current server to the servers specified in --servers
files and directories can contain spaces
works with Unix and Windows

Example XMLFILE:
<xml>
<copyfile   source='C:\Temp\Foo.txt' dest='D:\File\Bar.txt'
            callback='replace("A", "B").replace("B", "C")' >   <!-- callback 
string op on source text, optional dest -->
<copydir    source='C:\Test'         dest='D:\Testing'>                      
               <!-- dest is optional -->
<mirrordir  source='C:\Test'         dest='D:\Testing'>        <!-- delete 
dest first -->  <!-- dest is optional -->
<mkdir                               dest='D:\Testing'>
<rmdir                               dest='D:\Testing'>
</xml>
"""


import ftplib
from xml.sax import saxlib
from xml.sax.drivers import drv_xmlproc
import os
import sys
import string
import re
import getopt

Max_FTP_Attempts = 10  # will retry each high-level operation
verbose = 0

# get a temp dir
if   os.path.isdir('C:\\Temp'):
    tempdir = 'C:\\Temp'
elif os.path.isdir('/tmp'):
    tempdir = '/tmp'
else:
    raise "No valid tmpdir.  C:\\Temp and /tmp don't exist"


def usage(*args):
    sys.stdout = sys.stderr
    for msg in args: print msg
    print __doc__
    sys.exit(1)

def main():
    '''build list of source and destination files from xml configuration 
file
    and carry out the ftp'''

    global verbose

    ftptasks = {}    # data structure to maintain files to work with
    filesftpd = 0    # keep track of how many we have done
    ftpuser, ftppass, servers, xmlfile = '', '', [], ''   # options

    try:
        opts, args = getopt.getopt(sys.argv[1:], None, ['user=', 'pass=', 
'servers=', 'xmlfile=', 'verbose'])
    except getopt.error, msg:
            usage(msg)
    except TypeError, msg:
            usage(msg)

    for o, a in opts:
        if o == '--user'    : ftpuser = a
        if o == '--pass'    : ftppass = a
        if o == '--servers' : servers = a.split(',')
        if o == '--xmlfile' : xmlfile = a
        if o == '--verbose' : verbose = 1

    for option in 'ftpuser', 'ftppass', 'servers', 'xmlfile':
        value = eval(option) # like symbolic ref
        if not value: usage(option + " option must be specified")

    # parse file and get back data structure
    ftptasks = parse_xml(xmlfile)

    # check to ensure we have something to do
    numtasks = 0
    for key in ftptasks.keys(): numtasks += len(ftptasks[key])
    if numtasks == 0:
        sys.exit("Nothing to do\n")

    # validate input
    for file in ftptasks['copyfiles']:
        if not os.path.isfile(file['source']):
            sys.exit('Source file ' + file['source'] + ' does not exist\n')

    # validate and translate input - turn contents of copydirs and 
mirrordirs into a copyfile
    for dir in ftptasks['copydirs'] + ftptasks['mirrordirs']:
        if not os.path.isdir(dir['source']):
            sys.exit('Source directory ' + dir['source'] + ' does not 
exist\n')
        ftptasks['copyfiles'][len(ftptasks['copyfiles']):] = 
get_files(dir['source'], dir['dest'])

    # do the work on each server
    for server in servers:
        if verbose: print 'FTPing to', server
        ftpsession = FTPSession(server, ftpuser, ftppass)  # class keeps 
track of Session attributes and calls ftp funcs
                                                           # makes 
re-initing ftp on error much easier

        for dir in ftptasks['rmdirs']:
            if verbose: print '\tRemoving dir tree', dir['dest']
            ftpsession.rmdir(dir['dest'])

        for dir in ftptasks['mkdirs']:
            if verbose: print '\tMKDiring tree', dir['dest']
            ftpsession.mkdir(dir['dest'])

        for dir in ftptasks['mirrordirs']:
            if verbose: print '\tRemoving dir tree', dir['dest']
            ftpsession.rmdir(dir['dest'])

        for file in ftptasks['copyfiles']:
            if verbose: print '\tCopying', file['source'], 'to', 
file['dest']
            ftpsession.copyfile(file['source'], file['dest'])
            filesftpd += 1

    print 'Ftpd', filesftpd, 'files across', len(servers), 'servers:', 
servers


def parse_xml(xmlfile):
    '''handle the XML parsing'''

    files = {'copydirs': [], 'copyfiles': [], 'mkdirs': [], 'rmdirs': [], 
'mirrordirs': []}

    SAXparser = drv_xmlproc.SAX_XPParser()
    SAXparser.setDocumentHandler(DocumentHandler(files))  # override 
DocumentHandler for my tags
                                                          # files will be 
loaded with data structure
                                                          # representing xml 
file
    SAXparser.parse(xmlfile)
    return files


class DocumentHandler(saxlib.DocumentHandler):
    """set instance variables and append new dict for the attributes
    of each tag we're interested in"""

    def __init__(self, files):
        self.copydirs   = files['copydirs']
        self.copyfiles  = files['copyfiles']
        self.mkdirs     = files['mkdirs']
        self.rmdirs     = files['rmdirs']
        self.mirrordirs = files['mirrordirs']

    def startElement(self, name, attrs):
        if name == 'copyfile':
            validate_attributes(name, attrs.keys(), ['source', 'dest', 
'callback'])  # check to make sure we only get
                                                                             
         # what we expect
            if attrs.has_key('dest'):
                properdest = win_ftp_path(attrs.getValue('dest'))   # 
Unix-like path that will work on Win and Unix
            else:
                properdest = win_ftp_path(attrs.getValue('source')) # Assume 
same path on dest server if dest not given

            # can modify a file in place before ftpping
            # - currently can't have same name file in two different places 
use callback (latest will overwrite)
            # - callback must be an operation on a string
            if attrs.has_key('callback'):
                global tempdir
                # use tempfile to store modified file
                propersource  = tempdir + os.sep + 
os.path.basename(attrs.getValue('source'))
                newsourcetext = eval("open('" + attrs.getValue('source') + 
"').read()." + attrs.getValue('callback'))
                open(propersource, 'w').write(newsourcetext)
            else:
                propersource = attrs.getValue('source')

            self.copyfiles.append({'source': propersource, 'dest': properdest})

        elif name == 'copydir':
            validate_attributes(name, attrs.keys(), ['source', 'dest'])

            if attrs.has_key('dest'):
                properdest = win_ftp_path(attrs.getValue('dest'))
            else:
                properdest = win_ftp_path(attrs.getValue('source'))
            self.copydirs.append({'source': attrs.getValue('source'), 'dest': 
properdest})

        elif name == 'mkdir':
            validate_attributes(name, attrs.keys(), ['dest'])

            properdest = win_ftp_path(attrs.getValue('dest'))
            self.mkdirs.append({'dest': properdest})

        elif name == 'rmdir':
            validate_attributes(name, attrs.keys(), ['dest'])

            properdest = win_ftp_path(attrs.getValue('dest'))
            self.rmdirs.append({'dest': properdest})

        elif name == 'mirrordir':
            validate_attributes(name, attrs.keys(), ['source', 'dest'])

            if attrs.has_key('dest'):
                properdest = win_ftp_path(attrs.getValue('dest'))
            else:
                properdest = win_ftp_path(attrs.getValue('source'))
            self.mirrordirs.append({'source': attrs.getValue('source'), 'dest': 
properdest})

        elif name == 'xml':  # this is the root tag
            pass

        else:
            sys.exit(name + ' is not a supported tag\n')

def validate_attributes (tagname, attributes, valid_attributes):
    for attribute in attributes:
        if attribute not in valid_attributes:
            raise attribute + " attribute not supported for " + tagname + " 
tag.  Use " + str(valid_attributes)


def get_files(sourcedir, destdir):
    '''walk directory tree and build list of source and destination files
    based on a source and destination directory.  And do it recursively'''

    returnfiles = []
    sourcefiles = os.listdir(sourcedir)

    for sourcefile in sourcefiles:
        destfile   = destdir + '/' + sourcefile;      # destfile grows with 
the sourcefile
        sourcefile = sourcedir + os.sep + sourcefile;

        if os.path.isdir(sourcefile):
            returnfiles[len(returnfiles):] = get_files(sourcefile, destfile) 
  # it is a dir - get its files
        else:
            returnfiles.append({'source': sourcefile, 'dest': destfile})

    return returnfiles


class FTPSession:
    """store attributes for an ftp session and define functions for that 
session"""

    global Max_FTP_Attempts, verbose

    def __init__(self, server, user, password, maxattempts=Max_FTP_Attempts, 
verbose=verbose):
        self.server      = server
        self.user        = user
        self.password    = password
        self.maxattempts = maxattempts
        self.verbose     = verbose
        self.ftp         = self._init_ftp()

    def _init_ftp(self):
        """re-init the ftp connection.  Try to close it first"""
        try:
            self.ftp.quit()
        except:
            pass
        self.ftp = ftplib.FTP(self.server, self.user, self.password)

    def copyfile(self, source, dest):
        """copy the source to dest, making any necessary directories and 
retries along the way"""
        attempts = 0
        while 1:
            attempts += 1
            try:
                if os.path.isdir(source):           # shouldn't be dir, but 
just in case
                    self._mkdir(dest)
                else:
                    self._mkdir(unix_dirname(dest)) # make sure it's parent 
directory exists
                self._put(source, dest)             # will this fail on 
directories?
            except:
                if attempts >= self.maxattempts:
                    die(str(attempts) + " attempts to copy " + source + ' to 
' + dest +  " on " + self.server)
                self._init_ftp()                    # let's try it again 
with a new connection
            else:   # it worked
                return

    def mkdir(self, dir):
        """make a directory"""
        attempts = 0
        while 1:
            attempts += 1
            try:
                self._mkdir(dir)
            except:
                if attempts >= self.maxattempts:
                    die(str(attempts) + " attempts making directory " + dir 
+ " on " + self.server)
                self._init_ftp()
            else:
                return

    def rmdir(self, dir):
        """remove a directory and its contents"""
        attempts = 0
        while 1:
            attempts += 1
            try:
                self._rmdir(dir)
            except:
                if attempts >= self.maxattempts:
                    die(str(attempts) + " attempts removing directory " + 
dir + " on " + self.server)
                self._init_ftp()
            else:
                return

    def _put(self, source, dest):
        '''ftp source to dest'''

        if self.verbose: print '\t\tPutting', source, 'to', dest

        # get the size first to ensure a successfull transfer
        try:
            previous_size = self.ftp.size(dest)
        except ftplib.error_perm, error_resp:   # file does not exist
            error_resp_string = str(error_resp)
            if error_resp_string.find('No such file or directory') != -1 \
               or error_resp_string.find('system cannot find the file 
specified') != -1:
                previous_size = 0
            else:
                raise ftplib.error_perm, error_resp

        source_size = os.path.getsize(source)

        self.ftp.storbinary('STOR ' + dest, open(source, 'rb'), 1024)   # 
use binary mode - doesn't work with text
                                                                        # 
between different platforms

        post_delivery_size = self.ftp.size(dest)

        if post_delivery_size != source_size:
            error_msg = '\tERROR ftping ' + source + ' to ' + dest + ' on ' 
+ self.server + '\n' + \
                        '\t\tSource size: ' + source_size + '\n' + \
                        '\t\tDest size before ftp: ' + previous_size + '\n' 
+ \
                        '\t\tDest size after ftp: ', post_delivery_size + 
'\n'
            raise error_msg


    def _mkdir(self, dir):
        '''If a directory does not exist on the ftp server,
        make it and its subdirectories - recursively'''

        if self.verbose: print '\t\tMaking dir', dir

        try:
            self.ftp.cwd(dir)
            return
        except ftplib.error_perm:
            self._mkdir(unix_dirname(dir))

        self.ftp.mkd(dir) # dir didn't exist, but we know its dir does! 
(after recursive call)


    def _rmdir(self, dir):
        '''If a direcotry exists on the ftp server,
        remove its contents recursively'''

        if self.verbose: print '\t\tRemoving dir', dir

        try:
            self.ftp.cwd(dir)
        except ftplib.error_perm:  # dir doesn't exist - we're done
            return

        try:
            self.ftp.cwd(unix_dirname(dir))
            self.ftp.rmd(dir)
        except ftplib.error_perm, msg:  # directory isn't empty, remove its 
contents first
            self.ftp.cwd(dir)
            file_list = []
            self.ftp.dir(file_list.append)   # last arg is a callback for 
each line retrieved
            if len(file_list) == 0:
                raise('Error removing ' + dir + ' Error msg: ', msg + '\n')
            for file in get_files_from_ftpdir(file_list):
                full_path = dir + '/' + file                   # construct 
the full path
                try:
                    self.ftp.cwd(full_path)
                except ftplib.error_perm, msg:             # it's a file
                    self._rm(full_path)
                else:                                      # it's a 
directory - recursive call
                    self._rmdir(full_path)

            ftp.cwd(unix_dirname(dir))                     # now it's 
contents have been deleted
            ftp.rmd(dir)

    def _rm(self, file):
        '''Remove a file.'''

        if self.verbose: print '\t\tRemoving file', file

        self.ftp.cwd(unix_dirname(file))
        self.ftp.delete(file)


def win_ftp_path(path):
    """Unix-like path that will work on Windows and Unix - with Unix 
directory listing on Windows and proper Virtual dirs"""
    path = path.replace('\\', '/')
    if not path.startswith('/'):     # forgot leading slash
        path = '/' + path
    return path

def unix_dirname(dirname):
    """Get directory name from a unix path"""
    dirs = dirname.split('/')
    dir = '/'.join(dirs[:-1])
    return dir

def get_files_from_ftpdir(listings):
    """ftplisting has file in the 9th column"""
    matchstr = re.compile(r'\s+')
    file_list = []

    for row in listings:
        columns = re.split(matchstr, row, 8)
        file_list.append(columns[-1])
    return file_list


def die(msg):
    """write stderr message and traceback after an exception"""

    # shouldn't use traceback from the except clause
    import traceback
    sys.stdout.flush()
    sys.exit(msg + ":\n\t" + str(traceback.print_exc()) + "\n")


if __name__ == '__main__':
    main()







>From: "Matthew Brooks" <[EMAIL PROTECTED]>
>Reply-To: "Matthew Brooks" <[EMAIL PROTECTED]>
>To: "Boston Perl Mongers" <[EMAIL PROTECTED]>
>Subject: [Boston.pm] trouble with IIS driven FTP server using Net::FTP
>Date: Fri, 3 Aug 2001 22:04:48 -0400
>
>Anyone know if there is an issue with using Net::FTP to access a MS IIS
>driven FTP server?
>
>I'm getting an error that says the server can't find the specified file,
>even though the directory in question exists. (See below for debugged
>output) Also of note, just to see what was going on I added the lines...
>
>print $ftp->dir;
>$ftp->quit;
>exit;
>
>...to the script right before the $ftp->cwd($path) call that is failing and
>I noticed that none of the directories are showing up in the list, only the
>files in the ftp / directory appear.
>
>Here's the output. The lines that don't start with Net::FTP are generated 
>by
>print statements in the code:
>
>Connecting to 192.168.0.182...
>Net::FTP: Net::FTP(2.56)
>Net::FTP:   Exporter(5.562)
>Net::FTP:   Net::Cmd(2.18)
>Net::FTP:   IO::Socket::INET(1.25)
>Net::FTP:     IO::Socket(1.26)
>Net::FTP:       IO::Handle(1.21)
>
>Net::FTP=GLOB(0x1a750f8)<<< 220 WWW002 Microsoft FTP Service (Version 5.0).
>OK
>Logging in...
>Net::FTP=GLOB(0x1a750f8)>>> user mjbrooks
>Net::FTP=GLOB(0x1a750f8)<<< 331 Password required for mjbrooks.
>Net::FTP=GLOB(0x1a750f8)>>> PASS ....
>Net::FTP=GLOB(0x1a750f8)<<< 230 User mjbrooks logged in.
>OK
>Setting session to binary mode...
>Net::FTP=GLOB(0x1a750f8)>>> TYPE I
>Net::FTP=GLOB(0x1a750f8)<<< 200 Type set to I.
>OK
>Changing to /wholesite...
>Net::FTP=GLOB(0x1a750f8)>>> CWD /wholesite
>Net::FTP=GLOB(0x1a750f8)<<< 550 /wholesite: The system cannot find the file
>specified.
>Net::FTP=GLOB(0x1a750f8)>>> QUIT
>Net::FTP=GLOB(0x1a750f8)<<< 221
>
>


_________________________________________________________________
Get your FREE download of MSN Explorer at http://explorer.msn.com/intl.asp

Reply via email to