1

Тема: HTA JS: FTP-клиент пакетной загрузки/выгрузки файлов на хостинг(Ncftp)

HTA Javascript, Ncftp: FTP-клиент загрузки/выгрузки файлов на хостинг
Дано: Есть сайт на Joomla с установленным компонентом просмотра прайс-листов в формате Excel.
Задача: Обновлять прайсы на сайте, не залезая в админку, и не устанавливая громоздких FTP-клиентов. Технически, проблема сводится к регулярной выгрузке  с сайта по протоколу FTP определенного списка файлов, и его последующей загрузке на сайт.
Решение: Использовать консольные (portable) FTP-клиенты и простой графический интерфейс, сделанный на HTA

Изучение проблемы показало, что встроенный ftp-клиент Windows обладает ограниченной функциональностью (не поддерживается пассивный режим, т.е. не работает из-за NAT). Поэтому следует использовать сторонние FTP-клиенты.
Спасибо http://shlykov.info/10/, за качественный обзор консольных клиентов. Из предложенного списка консольных клиентов остановился на Ncftp.

Финальная разработка представляет собой следующие файлы:
ncftpput.exe, ncftpget.exe - консольные FTP-клиенты загрузки/выгрузки файлов по FTP. Взято с http://www.ncftp.com/ncftp/. Там же инструкция по параметрам командной строки.
simpleftp.hta - HTA-приложение: окном выбора локальной папки, и две кнопки Загрузить и Выгрузить, выполняющие пакетную загрузку/выгрузку файлов на FTP-сервер
Параметры соединения с FTP-сервером и список файлов "зашиты" в HTA-коде. Исправляйте соответственно Вашим требованиям.

Приложение задумано для простых офис-менеджеров (чем меньше кнопок и настроек, тем лучше).

В принципе, этот пример можно развить в полноценный FTP-клиент на HTA. Хорошая задача для кодеров-маньяков...:D

simpleftp.hta

<html>
  <head>
    <title>Обновление прайс-листов сайта http://MySite.ru</title>
    <hta:application
            applicationName="Обновление прайс-листов сайта http://MySite.ru"
            border="dialog"
            borderStyle="static"
            caption="yes"
            contextMenu="no"
            icon=""
            id="hta"
            innerBorder="no"
            maximizeButton="no"
            minimizeButton="no"
            navigable="yes"
            scroll="no"
            selection="no"
            showInTaskBar="yes"
            singleInstance="yes"
            sysMenu="yes"
            version="0.1" />

        <style>
            body, table { padding: 0px; margin: 0px; background-color: #F5F5F5; color: #000175; font-family: Tahoma; font-size: 12px;}
            fieldset {padding: 0px; margin: 7px 5px 5px 5px;}
            legend { font-weight: bold; font-style: oblique; font-family: Georgia; padding: 0px 2px;}
            div.app { width: 100%; height: 100%; padding: 0px; margin: 0px; }
            div.info { background-color: #CCCCCC; padding: 10px 5px 10px 10px; margin: 0px; font-size: 18pt; font-family: Georgia; cursor: pointer; color: White; border-bottom: 1px solid White; border-top: 1px solid White;}
            div.menu { background-color: #EEEEEE; padding: 7px 0px 7px 15px; margin: 0px; border-bottom: 1px solid White; font-family: Georgia; font-weight: bold; font-size: 14px;}
            div.body { display: none; margin: 0px; padding: 0px 5px;}
            div.log { padding: 5px 5px 7px 5px; margin: 0px; height: 170px; overflow: auto;}
            div.file { padding: 5px 5px 7px 5px; margin: 0px;}
            div.settings { padding: 5px 5px 7px 5px; margin: 0px; height: 250px;}
            div.cb {cursor: pointer; border: 1px solid #F5F5F5;}
            span.menu { cursor: pointer; padding: 0px 3px;}
            span.space {margin-right: 15px;}
            button, {background: #EEEEEE; border: 1px solid #CCCCCC; margin: 1px;}
            button.menu { cursor: pointer; background-color: #EEEEEE; margin: 0px; padding: 0px; border: none; color: #000175; font-family: Georgia; font-weight: bold; font-size: 14px;}
            input { border: 1px solid #DFDFDF; margin: 0px; height: 1.6em;}
            input.button {background: #EEEEEE; border: 1px solid #CCCCCC;}
            input.checkbox {cursor: pointer; border: none; margin: 0px; padding: 0px;}
        </style>

<script type="text/javascript">
    var windowWidth    =    360;
    var windowHeight = 437;
    window.resizeTo(windowWidth,windowHeight);
    var x = (window.screen.availWidth - windowWidth)/2;
    var y = (window.screen.availHeight - windowHeight)/2;
    window.moveTo(x,y);

window.onload = function(){
    showFiles();
    checkJScript();
    if(!dirButton.getAttribute("disabled"))dirButton.focus();
    writeLog(new Array("версия: " + hta.version));
}
            
function showFiles(){config.style.display="none";info.style.display="none";files.style.display="block";}
function showConfig(){info.style.display="none";files.style.display="none";config.style.display="block";}
function showInfo(){config.style.display="none";files.style.display="none";info.style.display="block";}
function mouseOver(item){item.style.background="#EEEEEE";item.style.border="1px solid #CCCCCC";}
function mouseOut(item){item.style.background="#F5F5F5";item.style.border="1px solid #F5F5F5";}

// variables
var settings = new Array(3);

var oShell = new ActiveXObject("Wscript.Shell");
var shell = new ActiveXObject("Shell.Application");
var fso = new ActiveXObject("Scripting.FileSystemObject");
var word = null;
var dir = null;

// тут зашиты параметры соединения с FTP и пути к каталогу загрузки-выгрузки на сервере
// меняйте соответственно вашим требованиям
// <PathToFile><PathToDir><FileName> - макроподстановки, для имен файлов и пути к локальному каталогу, используются в алгоритме

var DownloadCommand = "ncftpget -u ftp_user -p ftp_password -P 21 mysite.ru <PathToDir> /www/mysite.ru/components/com_excel/prices/<FileName>" ;
var UploadCommand = "ncftpput -u ftp_user -p ftp_password -P 21 mysite.ru /www/mysite.ru/components/com_excel/prices/ <PathToFile>" ;


//список файлов с прайс-листами
//в цикле по списку выполняется макроподстановка вместо <FileName>
//измените на Ваш список
var xlsFileCount = 20;
var xlsFileArray = new Array(
"1051874570-4.xls",
"1092812908-5.xls",
"1136255424-4.xls",
"1238705087-4.xls",
"1375786074-5.xls",
"1754316211-3.xls",
"1782502850-3.xls",
"2722188756-5.xls",
"2801637963-4.xls",
"2911438163-3.xls",
"3053163155-5.xls",
"3249793829-5.xls",
"3486591861-5.xls",
"3822741412-5.xls",
"3905233543-4.xls",
"4068742619-4.xls",
"4082181836-3.xls",
"451263677-1.xls",
"649715513-4.xls",
"670720701-3.xls"
)
            
// message box settings
var mbIndex = 1;
var msgBoxPathStr = "Папка загрузки/выгрузки:";
var msgselectDirNoExists = "Папка не найдена. Введите путь к существующей папке! ";
var msgselectDirStr = "Выберите папку с файлами!";
var msgDownloadFileOK = "Прайс-лист загружен с веб-сайта:";
var msgDownloadFilesOK = "Все прайс-листы загружены с веб-сайта в каталог:";
var msgUploadFileOK = "Прайс-лист выгружен на веб-сайт:";
var msgUploadFilesOK = "Все прайс-листы из каталога выгружены на веб-сайт:";
var msgErrorStr = "Ошибка:";

function disableInputs(){
    path.setAttribute('disabled', true);
    dirButton.setAttribute('disabled', true);
    DownloadButton.setAttribute('disabled', true);
    UploadButton.setAttribute('disabled', true);
}
function enableInputs(){
    path.setAttribute('disabled', false);
    dirButton.setAttribute('disabled', false);
    DownloadButton.setAttribute('disabled', false);
    UploadButton.setAttribute('disabled', false);
}
function isInputsEnabled(){
    return !dirButton.getAttribute("disabled") && !DownloadButton.getAttribute("disabled")&& !UploadButton.getAttribute("disabled");
}
function checkJScript(){
    try{
        if(ScriptEngineMajorVersion() < 5){
            disableInputs();
            throw new Error("Старая версия JScript;Необходимо: "+ ScriptEngine() + " 5.6;Установленно: " + ScriptEngine() + " " + ScriptEngineMajorVersion() + "." + ScriptEngineMinorVersion() + "." + ScriptEngineBuildVersion());
        }
    } catch(ex) {
        printError(ex);
    }
}
function printMsg(div){
    var div = document.getElementById("log");
    div.appendChild(div);
}
function printError(exception){
    var error = new Array(exception.name + " (" + (exception.number & 0xFFFF) + "):");
    var disc = exception.description.split(";");
    for(var i=0;i<disc.length;i++){
        error.push(disc[i]);
    }
    writeLog(error, true);
}
function PathChanged(){
    try {
        path.value = path.value.replace('"','');
        path.value = path.value.replace('"','');
        path.value = fso.GetAbsolutePathName(path.value);
        if(fso.FolderExists(path.value)){
            dir = path.value;
            writeLog(new Array(msgBoxPathStr, dir), false);
            DownloadButton.setAttribute('disabled', false);
            DownloadButton.focus();
            UploadButton.setAttribute('disabled', false);
        }else{
            dir = path.value;
            writeLog(new Array(msgselectDirNoExists, dir), true);
            DownloadButton.setAttribute('disabled', true);
            UploadButton.setAttribute('disabled', true);
        }
    } catch (ex) {
        printError(ex);
    }
}
function selectDir(){
    try {
        var folder = shell.BrowseForFolder(0, msgselectDirStr, 16, 0);
        if (folder != null) {
            dir = folder.Self.Path;
            path.value = folder.Self.Path;
            writeLog(new Array(msgBoxPathStr, dir), false);
            DownloadButton.setAttribute('disabled', false);
            DownloadButton.focus();
            UploadButton.setAttribute('disabled', false);
        }
    } catch (ex) {
        printError(ex);
    }
}
function writeLog(msg, error){
    var div = document.createElement("div");
    if(error){
        div.style.background = "#FFE8E1";
    } else {
        div.style.background = "#ECF0EE";
    }
    div.style.padding = "2px 2px 2px 2px";
    div.style.margin = "2px 0px 2px 0px";
    var node = document.createTextNode("[" + mbIndex++ + "] ");
    div.appendChild(node);
    for(var i=0; i<msg.length; i++){
            if(msg[i]!=null){
            node = document.createTextNode(msg[i]);
            div.appendChild(node);
            node = document.createElement("br");
            div.appendChild(node);
        }
    }
    var log = document.getElementById("log");                
    if(log.childNodes.length > 0){
        log.insertBefore(div, log.childNodes[0]);
    } else {
        log.appendChild(div);
    }
}
function DownloadPrices(){
    try {
        disableInputs();
        if(dir == null || dir == ""){
            throw new Error(msgselectDirStr);

        }
        for(i=0;i<xlsFileCount;i++){
            sCommandLine = "\""+dir+"\"";
            sCommandLine = DownloadCommand.replace("<PathToDir>",sCommandLine);
            sCommandLine = sCommandLine.replace("<FileName>",xlsFileArray[i]);
            if(sCommandLine.length > 0){oShell.Run(sCommandLine,7,true);};
            writeLog(new Array(msgDownloadFileOK, xlsFileArray[i]), true);
        }

        if(sCommandLine.length > 0){oShell.Run(sCommandLine,7,true);    };
        writeLog(new Array(msgDownloadFilesOK, dir), false);

    } catch (ex) {
        printError(ex);
    } finally {
        dir = "";
        path.value = "";
        disableInputs();
        path.setAttribute('disabled', false);
        dirButton.setAttribute('disabled', false);
    }
}
function UploadPrices(){
try {
    disableInputs();
    if(dir == null || dir == ""){
        throw new Error(msgselectDirStr);
    }
        for(i=0;i<xlsFileCount;i++){
            sCommandLine ="";
            sCommandLine = sCommandLine + dir +"\\"+xlsFileArray[i];//"test.test";
            if(fso.FileExists(sCommandLine)){
                sCommandLine = "\""+sCommandLine+"\"";
                sCommandLine = UploadCommand.replace("<PathToFile>",sCommandLine);
                if(sCommandLine.length > 0){oShell.Run(sCommandLine,7,true);};
                writeLog(new Array(msgUploadFileOK, xlsFileArray[i]), true);
            }
        }
    } catch (ex) {
        printError(ex);
    } finally {
        writeLog(new Array(msgUploadFilesOK, dir), false);
        dir = "";
        path.value = "";
        disableInputs();
        path.setAttribute('disabled', false);
        dirButton.setAttribute('disabled', false);
    }
}
</script>
</head>
<body>
<div class="info">Обновление прайс-листов сайта http://MySite.ru</div>
    <div style="padding: 5px"><button style="width:100%;" onclick="DownloadPrices()" id="DownloadButton" disabled="disabled" title="">Загрузить прайсы с веб-сервера</button></div>
    <fieldset>
        <legend>Папка</legend>
        <div class="file">
        <input type="text" id="path" style="width:90%;border-right:0px;" onchange="PathChanged();">
        <input type="button" value=" &raquo; " onclick="selectDir();" id="dirButton" class="button" title="">
        </div>
    </fieldset>
    <div style="padding: 5px"><button style="width:100%;" onclick="UploadPrices()" id="UploadButton" disabled="disabled" title="">Выгрузить прайсы на веб-сервер</button></div>
    <fieldset>
        <legend>Журнал</legend>
        <div class="log" id="log"></div>
    </fieldset>
</div>
<div id="files" class="body"></div>
<div id="config" class="body"></div>
<div id="info" class="body"></div>
</body>
</html>

Это мое первое HTA-приложение. Спасибо авторам doc2fb.hta за код, использовавший в качестве образца.

2

Re: HTA JS: FTP-клиент пакетной загрузки/выгрузки файлов на хостинг(Ncftp)

shell.application заменит все в две строки

Я конечно далек от мысли... (с)

3

Re: HTA JS: FTP-клиент пакетной загрузки/выгрузки файлов на хостинг(Ncftp)

smaharbA пишет:

shell.application заменит все в две строки

Пробовал. Не заменит. Встроенный в Windows FTP-клиент не поддерживает passive mode, то есть, не работает из-за NAT.
Кроме того, в HTA нарисован небольшой GUI...

4

Re: HTA JS: FTP-клиент пакетной загрузки/выгрузки файлов на хостинг(Ncftp)

shell.application заменит, клиент ftp.exe тут не приделах

Я конечно далек от мысли... (с)

5

Re: HTA JS: FTP-клиент пакетной загрузки/выгрузки файлов на хостинг(Ncftp)

smaharbA пишет:

shell.application заменит, клиент ftp.exe тут не приделах

пример можете дать. пару строк кода shell.application ака ftp-клиент

6

Re: HTA JS: FTP-клиент пакетной загрузки/выгрузки файлов на хостинг(Ncftp)

Шел=new ActiveXObject("Shell.Application");
Имя="Вася";
Имя="";
Пароль="Пупкин";
УРЛ="ftp.drweb.com/pub/drweb";
ПапкаФТП=Шел.NameSpace("ftp://" + (Имя ? Имя + "@" + Пароль + ":" : "") + УРЛ);
Стр="";
Темы=new Enumerator(ПапкаФТП.Items());
for (;!Темы.atEnd();Темы.moveNext()) Стр=Стр+Темы.item().Path+"\n";
alert(Стр);
Я конечно далек от мысли... (с)

7 (изменено: trash0000, 2012-08-09 14:15:24)

Re: HTA JS: FTP-клиент пакетной загрузки/выгрузки файлов на хостинг(Ncftp)

Пример получилось переделать.
Только при копировании файлов не смог подавить диалоговые окна на перезапись файлов, если они уже существуют.
Локально я могу проверить и удалить файлы перед загрузкой с помощью FileSystemObject.
А при выгрузке на удаленный сервер удалить файлы не получается.
Флаги CopyHere не работают, как будто метод запускается без флагов.
Операционная система Windows server 2008.

Как быть?

// описание флагов для VBA с сайта Microsoft
//    Const FOF_SILENT = &H4&
//    Const FOF_RENAMEONCOLLISION = &H8&
//    Const FOF_NOCONFIRMATION = &H10&
//    Const FOF_ALLOWUNDO = &H40&
//    Const FOF_FILESONLY = &H80&
//    Const FOF_SIMPLEPROGRESS = &H100&
//    Const FOF_NOCONFIRMMKDIR = &H200&
//    Const FOF_NOERRORUI = &H400&
//    Const FOF_NOCOPYSECURITYATTRIBS = &H800&
//    Const FOF_NORECURSION = &H1000&
//    Const FOF_NO_CONNECTED_ELEMENTS = &H2000&
//
//cFlags = FOF_SILENT + FOF_NOCONFIRMATION + FOF_NOERRORUI

var shell = new ActiveXObject("Shell.Application");
var UploadUrl= "ftp://user:password@my-ftp-server.ru:21/RemoteDirectory/"
var dir = "C:\TEMP\";
var cFlags = 4+16+1024;
    try {
        var LocalFolder=shell.namespace(dir);
        var LocalFiles=new Enumerator(LocalFolder.Items());
        var FtpFolder=shell.namespace(UploadUrl);

        for (;!LocalFiles.atEnd();LocalFiles.moveNext()){
            FtpFolder.CopyHere(LocalFiles.item(),cFlags);
        }
//        writeLog(new Array(msgUploadFilesOK, dir), false);
    }