#!/usr/bin/python
#
# Copyright (C) 2004  Geoffrey T. Dairiki <dairiki@dairiki.org>
#
# This file is part of Pyfsp, a pure python implementation
# of the FSP file transfer protocol.
#
# Pyfsp is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# Pyfsp is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
import sys
import os
import cmd
import shlex
import fsplib.client


class _autohelp(type):
    def __new__(type_, name, bases, dict):
        for m in dict.keys():
            if m.startswith('do_') and dict[m].__doc__:
                helpname = 'help_' + m[3:]
                if dict.has_key(helpname):
                    continue
                doc = dict[m].__doc__
                def help(self):
                    print doc
                dict[helpname] = help
        return type.__new__(type_, name, bases, dict)
    
class Program(cmd.Cmd, object):

    __metaclass__ = _autohelp
    
    prompt = 'fsp> '

    def __call__(self, host=None, port=fsplib.DEFAULT_FSP_PORT, password=None):
        self.fsp = fsplib.client.FspClient()
        if host:
            msg = self._open(host, port)
            if password:
                self.fsp.set_password(password)
        else:
            msg = None
        try:
            self.cmdloop(msg)
        finally:
            if self.fsp.connected:
                self.fsp.disconnect()

    def onecmd(self, line):
        try:
            return cmd.Cmd.onecmd(self, line)
        except fsplib.ServerError, ex:
            print 'server error: %s' % ex
        #except fsplib.client.ClientError, ex:
        #    print ex
        except Exception, ex:
            print '%s.%s: %s' % (ex.__class__.__module__,
                                 ex.__class__.__name__, ex)
    def help_open(self):
        print """open host [port]
        Establish a connection to the specifed host.  An optional port number
        may be supplied.  The default port is %d.
        """ % fsplib.DEFAULT_FSP_PORT
    def do_open(self, line):
        """open host [port]
        Establish a connection to the specifed host.  An optional port number
        may be supplied.  The default port is %d.
        """ % fsplib.DEFAULT_FSP_PORT
        self._open(*shlex.split(line))
    def _open(self, host, port=fsplib.DEFAULT_FSP_PORT):
        info = self.fsp.connect(host, int(port))
        return "connected %s:%d (%s)" % (self.fsp.host, self.fsp.port,
                                         info.version.strip())

    def do_password(self, line):
        """password password
        Set the current password.  Note that it is possible for each
        directory on the FSP server can have a different password,
        so this can get confusing.
        """
        self.fsp.set_password(line)

    def do_close(self, line):
        """close
        Terminate the FSP session with the remote server, and return
        to the command interpreter.
        """
        self.fsp.disconnect()

    # FIXME: todo

    # do_bookmark
    # do_bookmarks
    # do_chmod ?
    # do_pdir
    # do_pls
    # do_version

    # Not needed?
    # do_set (or maybe password could be a variable?)
    # do_show
    # do_type (transfer type)
    
    # do_lcd (?)
    # do_lls
    # do_lmkdir
    # do_lpage
    # do_lpwd
    # do_lrename
    # do_lrm
    # do_lrmdir
    

    def do_cat(self, line):
        """cat remote-file [...] 
        This downloads the specified file(s) and outputs them directly
        to the screen.  You will probably find the page command more
        useful.
        """
        for fn in shlex.split(line):
            self.getfile(fn, sys.stdout)
                
    def do_cd(self, line):
        """cd remote-directory
        Change the working directory on the remote machine to
        remote-directory.
        """
        dir = self.fsp.chdir(line)
        if dir.readme:
            print dir.readme
        print "FLAGS = 0x%02x" % dir.flags


    def do_dir(self, line):
        """dir [options] [remote-directory] [local-file]
        Print a listing of the contents of a directory on the remote
        machine.  (FIXME: more)
        """
        # FIXME: (ls -CF)
        print self.fsp.listdir(line)

    def do_get(self, line):
        """get [options] remote-file [...]
        Download file(s) from the server.
        """
        # FIXME: wildcards, options, status output
        for fn in shlex.split(line):
            self.getfile(fn, open(os.path.basename(fn), "w"))
        
    do_ls = do_dir                      # FIXME:

    def do_mkdir(self, line):
        """mkdir remote-path
        Create a new directory on the server.
        """
        newdir = self.fsp.mkdir(line)
        print "FLAGS = 0x%02x" % newdir.flags
        
    def do_page(self, line):
        """page remote-file [...]
        This downloads the specified file(s) and views them using the
        program specified in your $PAGER environment variable.
        """
        for fn in shlex.split(line):
            self.getfile(fn, self.pager())

    def do_put(self, line):
        """put [options] remote-file [...]
        Upload file(s) to the server.
        """
        # FIXME: wildcards, options, status output
        for fn in shlex.split(line):
            infp = open(fn)
            outfp = self.fsp.open(os.path.basename(fn), "w")
            buf = infp.read(1024)
            while buf:
                # FIXME: buffering?
                nw = outfp.raw_write(buf)
                buf = buf[nw:]
                if not buf:
                    buf = infp.read(1024)
            outfp.close()

    def do_pwd(self, line):
        """pwd
        Display the current working directory on the server.
        """
        print self.fsp.cwd

    def do_rename(self, line):
        """rename remote-file remote-path
        Rename a file on the server.
        """
        src, dst = shlex.split(line)
        self.fsp.rename(src, dst)
        
    def do_rm(self, line):
        """rm remote-file [...]
        Delete files on the server.
        """
        # FIXME: wildcards, options, status output
        for fn in shlex.split(line):
            self.fsp.remove(fn)
        
    def do_rmdir(self, line):
        """rmdir remote-directory
        Remove a directory on the server.
        """
        # FIXME: this doesn't seem to work (is it an fspd problem?)
        self.fsp.rmdir(line)
        
    def do_debug(self, line):
        """show protocol-level debugging information
        This command is mostly for internal testing.  You could type

            debug 1

        to turn debugging mode on.  Then  you  could  see  all  messages
        between  the  program and the remote server, and things that are
        only printed in debugging mode. 
        """
        self.fsp.set_debuglevel(int(line))
        
    def do_bye(self, line):
        """bye
        Terminate the FSP seesion and exit fsp.  An end of file will
        also terminate the session and exit.
        """
        if self.fsp.connected:
            self.fsp.disconnect()
        return 'quit'

    def do_quit(self, line):
        """quit
        A synonym for bye.
        """
        return self.do_bye(line)

    def do_EOF(self, line):
        """An end of file will terminate the session and exit."""
        self.do_bye(line)
        print
        return 'quit'

    def getfile(self, path, outfp):
        fp = self.fsp.open(path)
        buf = fp.raw_read()
        while buf:
            outfp.write(buf)
            buf = fp.raw_read()

    def pager(self):
        pager = os.environ.get('PAGER', 'less')
        try:
            return os.popen(pager, 'w')
        except:
            return os.popen('more', 'w')


main = Program()
if __name__ == '__main__':
    main()
