
(********************************************************************)
(*                                                                  *)
(*  ftp7.sd7      FTP internet file transfer program                *)
(*  Copyright (C) 2011  Thomas Mertes                               *)
(*                                                                  *)
(*  This program 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.             *)
(*                                                                  *)
(*  This program 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., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


$ include "seed7_05.s7i";
  include "osfiles.s7i";
  include "ftp.s7i";
  include "keybd.s7i";
  include "console.s7i";
  include "editline.s7i";


const proc: writeHelp is func
  begin
    writeln("Accepted commands:");
    writeln("  ascii, binary, bye, cd, delete, dir, exit, get, help, lcd, ls, ls -l,");
    writeln("  mkdir, modtime, put, pwd, quit, rename, rmdir, size");
    writeln;
  end func;


const proc: longFileListing (inout fileSys: aFileSys, in var string: directory) is func
  local
    var array string: dir is 0 times "";
    var integer: currentYear is 0;
    var string: fileName is "";
    var string: filePath is "";
    var boolean: continue is TRUE;
    var string: line is "";
    var fileType: aFileType is FILE_ABSENT; 
    var time: modificationTime is time.value;
    const string: fileTypeIndicator is " ?-dcbfls";
    const array string: monthName is [1] (
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
  begin
    if directory = "" then
      dir := readDir(aFileSys, ".");
    else
      dir := readDir(aFileSys, directory);
      directory &:= "/";
    end if;
    currentYear := time(NOW).year;
    for fileName range dir do
      if busy_getc(KEYBOARD) = KEY_CTL_C then
        writeln(" ***** Terminated.");
        continue := FALSE;
      end if;
      if continue then
        filePath := directory & fileName;
        aFileType := fileType(aFileSys, filePath);
        line := str(fileTypeIndicator[succ(aFileType)]);
        line &:= "rwxrwxrwx 1 user users ";
        if aFileType = FILE_ABSENT then
          line &:= 0 lpad 10 <& " ";
          modificationTime := time(NOW);
        else
          line &:= bigFileSize(aFileSys, filePath) lpad 10 <& " ";
          modificationTime := getMTime(aFileSys, filePath);
        end if;
        line &:= monthName[modificationTime.month];
        line &:= modificationTime.day lpad 3;
        if modificationTime.year = currentYear then
          line &:= modificationTime.hour lpad 3 <& ":";
          line &:= modificationTime.minute lpad0 2;
        else
          line &:= modificationTime.year lpad 6;
        end if;
        line &:= " ";
        line &:= noCtrlChars(fileName);
        writeln(line);
      end if;
    end for;
  end func;


const proc: fileListing (inout fileSys: aFileSys, in string: directory) is func
  local
    var array string: dir is 0 times "";
    var string: name is "";
    var boolean: continue is TRUE;
  begin
    if directory = "" then
      dir := readDir(aFileSys, ".");
    else
      dir := readDir(aFileSys, directory);
    end if;
    for name range dir do
      if busy_getc(KEYBOARD) = KEY_CTL_C then
        writeln(" ***** Terminated.");
        continue := FALSE;
      end if;
      if continue then
        writeln(noCtrlChars(name));
      end if;
    end for;
  end func;


const proc: directoryListing (inout ftpFileSys: ftp, in string: directory) is func
  local
    var array string: dir is 0 times "";
    var string: line is "";
    var boolean: continue is TRUE;
  begin
    if directory = "" then
      dir := listDir(ftp, ".");
    else
      dir := listDir(ftp, directory);
    end if;
    for line range dir do
      if busy_getc(KEYBOARD) = KEY_CTL_C then
        writeln(" ***** Terminated.");
        continue := FALSE;
      end if;
      if continue then
        writeln(line);
      end if;
    end for;
  end func;


const proc: copyFtpFile (inout fileSys: sourceSys, in string: sourcePath,
    inout fileSys: destSys, in string: destPath, in boolean: asciiTransfer) is func
  local
    var string: asciiMode is "";
    var file: source is STD_NULL;
    var file: dest is STD_NULL;
  begin
    if asciiTransfer then
      asciiMode := "t";
    end if;
    source := open(sourceSys, sourcePath, "r" & asciiMode);
    if source <> STD_NULL then
      dest := open(destSys, destPath, "w" & asciiMode);
      if dest <> STD_NULL then
        copyFile(source, dest);
        close(dest);
      else
        writeln(" ***** Cannot open destination: " <& destPath);
      end if;
      close(source);
    else
      writeln(" ***** Cannot open source: " <& sourcePath);
    end if;
  end func;


const proc: execute (inout ftpFileSys: ftp, in string: command) is func
  local
    var array string: dir is 0 times "";
    var string: name is "";
    var integer: blankPos is 0;
    var boolean: continue is TRUE;
  begin
    # writeln(command);
    if startsWith(command, "ls -l") then
      longFileListing(ftp, trim(command[7 ..]));
    elsif startsWith(command, "ls") then
      fileListing(ftp, trim(command[4 ..]));
    elsif startsWith(command, "dir") then
      directoryListing(ftp, trim(command[5 ..]));
    elsif startsWith(command, "cd ") then
      chdir(ftp, trim(command[4 ..]));
    elsif startsWith(command, "mkdir ") then
      mkdir(ftp, trim(command[7 ..]));
    elsif startsWith(command, "rmdir ") then
      rmdir(ftp, trim(command[7 ..]));
    elsif startsWith(command, "delete ") then
      removeFile(ftp, trim(command[8 ..]));
    elsif startsWith(command, "rename ") then
      name := trim(command[8 ..]);
      if name = "" then
        writeln(" ***** From-name missing");
      else
        blankPos := pos(name, ' ');
        if blankPos <> 0 then
          moveFile(ftp, name[.. pred(blankPos)], name[succ(blankPos) ..]);
        else
          writeln(" ***** To-name missing");
        end if;
      end if;
    elsif command = "pwd" then
      writeln(getcwd(ftp));
    elsif startsWith(command, "size ") then
      writeln(fileSize(ftp, trim(command[6 ..])));
    elsif startsWith(command, "modtime ") then
      writeln(getMTime(ftp, trim(command[9 ..])));
    elsif startsWith(command, "lcd ") then
      chdir(trim(command[5 ..]));
    elsif startsWith(command, "ascii") then
      setAsciiTransfer(ftp, TRUE);
    elsif startsWith(command, "binary") then
      setAsciiTransfer(ftp, FALSE);
    elsif startsWith(command, "get ") then
      copyFtpFile(ftp, trim(command[5 ..]), osFiles, trim(command[5 ..]),
          getAsciiTransfer(ftp));
    elsif startsWith(command, "put ") then
      copyFtpFile(osFiles, trim(command[5 ..]), ftp, trim(command[5 ..]),
          getAsciiTransfer(ftp));
    elsif startsWith(command, "!ls -l") or startsWith(command, "!dir") then
      longFileListing(osFiles, trim(command[7 ..]));
    elsif startsWith(command, "!ls") then
      fileListing(osFiles, trim(command[4 ..]));
    elsif command = "!pwd" then
      writeln(getcwd(osFiles));
    elsif startsWith(command, "!mkdir ") then
      mkdir(osFiles, trim(command[7 ..]));
    elsif startsWith(command, "!rmdir ") then
      rmdir(osFiles, trim(command[7 ..]));
    elsif startsWith(command, "help") then
      writeHelp;
    elsif command <> "" then
      writeln(" ***** Invalid command.");
    end if;
  end func;


const proc: main is func
  local
    var string: command is "";
    var integer: ftpControlPort is defaultFtpControlPort;
    var ftpFileSys: ftp is fileSys.value;
  begin
    writeln("Ftp7 Version 1.0 - FTP internet file transfer program");
    writeln("Copyright (C) 2011 Thomas Mertes");
    writeln("This is free software; see the source for copying conditions.  There is NO");
    writeln("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.");
    writeln("Ftp7 is written in the Seed7 programming language");
    writeln("Homepage: http://seed7.sourceforge.net");
    writeln;
    writeln("usage: ftp7 host [port]");
    writeln;
    if length(argv(PROGRAM)) = 0 then
      writeln("Use  ftp7 -?  to get more information about ftp7.");
      writeln;
    elsif length(argv(PROGRAM)) = 1 and argv(PROGRAM)[1] = "-?" then
      writeHelp;
    else
      writeHelp;
      OUT := STD_CONSOLE;
      IN := openEditLine(KEYBOARD, OUT);
      if length(argv(PROGRAM)) >= 2 and isDigitString(argv(PROGRAM)[2]) then
        block
          ftpControlPort := integer parse (argv(PROGRAM)[2]);
        exception
          catch RANGE_ERROR: writeln(" ***** Port number too big. Port " <&
              defaultFtpControlPort <& " used instead.");
        end block;
      end if;
      ftp := openFtp(argv(PROGRAM)[1], ftpControlPort);
      # setActiveMode(ftp, TRUE);
      if ftp = fileSys.value then
        writeln(" ***** Could not connect to " <& argv(PROGRAM)[1] <&
                " at port " <& ftpControlPort <& ".");
      else
        writeln("Connected with: " <& argv(PROGRAM)[1] <&
                " at port " <& ftpControlPort <& ".");
        write("ftp7> ");
        readln(command);
        while command <> "exit" and command <> "bye" and command <> "quit" do
          block
            execute(ftp, command);
          exception
            catch FILE_ERROR: writeln(" ***** Command failed.");
          end block;
          write("ftp7> ");
          readln(command);
        end while;
        close(ftp);
      end if;
    end if;
  end func;
