|
Глава 6. CsShopper:
FTP-клиент
Джон Пенман
Отправляйтесь в Internet за бесплатным
барахлом! В этом вам поможет компонент, выполняющий функции FTP-клиента,
и полноцен ное приложение для пересылки файлов, построенное на его основе.
Популярность Internet в немалой степени
обусловлена возможностью обмена информацией между компьютерами. Такой обмен
становится возможным благодаря протоколу пересылки файлов FTP (File Transfer
Protocol) — одному из самых старых протоколов, используемых в Internet.
Формальная спецификация используемого в настоящее время протокола FTP содержится
в документе RFC959.
Протокол FTP, как и другие Internet-протоколы,
берет свое начало в классической модели клиент/сервер. FTP-сервер иногда
представляется мне в виде старомодного продавца, который снимает товар
с полки и передает его покупателю (FTP-клиенту). В этой главе мы реализуем
компонент Delphi с весьма подходящим именем CsShopper, выполняющий функции
FTP-клиента.
Компонент CsShopper построен на основе
CsSocket — простейшего компонента-оболочки для функций Winsock API, созданного
в главе 5. CsSocket обеспечивает базовые возможности, необходимые для работы
протокола FTP в сети TCP/IP. Таким образом, о мелочах есть кому позаботиться,
и мы можем сразу же прейти к более пристальному рассмотрению процесса FTP
глазами клиента.
Вас обслуживают?
По умолчанию FTP-сервер всегда ожидает, что
клиент инициирует соедине ние через TCP-порт с номером 21. Это соединение
(оно называется управляющим соединением, control connection) остается
открытым до тех пор, пока либо клиент, либо сервер не закроет его со своей
стороны. Через установлен ное соединение клиент и сервер обмениваются командами
FTP и кодами ответов соответственно. В командах Internet-протоколов обычно
используется обычный англоязычный текст (чаще всего в верхнем регистре).
Это остается справедливым даже при взаимодействиях между программами. Причина
заключается в том, что Internet первоначально работал только с 7-разрядной
ASCII-кодировкой, которая была (и остается) «наименьшим общим знамена телем»
для общения двух систем — компьютерных или любых других.
Это обстоятельство не лучшим образом сказывается
на скорости работы, но зато человеку становится значительно легче уследить
за взаимодействием двух Internet-программ. На каждую команду, полученную
от клиента, сервер обычно посылает код ответа. Код состоит из трех цифр,
за которыми следует дефис или пробел, а затем — некоторый текст. Типичные
сообщения могут выглядеть следующим образом:
200 PORT command successful.
230-Welcome to your I-SITE Internet server!
Дефис или пробел, следующий за числовым
кодом, содержит важную для клиента информацию. Дефис сообщает клиенту о
том, что данное сообщение является комментарием и его можно спокойно игнорировать.
Пробел указывает клиенту на необходимость перехода к следующей фазе текущей
операции. Текст, который идет дальше, обычно содержит информацию о статусе
или инструкцию для пользователя.
Диаграмма, изображенная на рис. 6.1, описывает
взаимодействие клиента с сервером во время регистрации. FTP-сеанс начинается
с посылки клиентом команды USER, за которой следует имя пользователя, и
получения со стороны сервера кода ответа, состоящего из трех цифр. Если
имя пользовате ля признается допустимым, сервер отвечает кодом 331 или
230. При недопустимом имени пользователя генерируется код 4xx или
5xx, где xx описывает код конкретной ошибки.
Ответ 230 означает, что имя пользователя
признано допустимым и для доступа к системе не требуется никакой дополнительной
информации. Сервер обычно выдает этот код в ответ при знаменитой «анонимной»
регистрации пользователей. Ответ 331 означает, что имя пользователя также
признано допустимым, но для доступа к системе необходим пароль. В этом
случае клиент посылает команду PASS, за которой следует пароль.
Неверный пароль вызывает ответ 4xx
или 5xx, свидетельствующий об ошибке. Если пароль принят, сервер
может послать код 230, чтобы сообщить о завершении регистрации. Если для
регистрации необходимы сведения об используемых ресурсах (account), сервер
снова отвечает кодом 331, чтобы клиент послал команду ACCT и требуемые
сведения.
Рис. 6.1. Регистрация FTP-клиента на FTP-сервере
После того как соединение будет успешно
установлено, клиент может продолжить посылку команд. Однако при возникновении
проблемы (например, посылке команды с неверным синтаксисом) или слишком
большом количестве пользователей, работающих в системе, сервер посылает
код 4xx или 5xx и закрывает соединение.
Компонент CsShopper
CsShopper происходит от VCL-компонента CsSocket
из главы 5. В нем класс TCsSocket используется для выполнения повседневных
задач — загрузки Winsock DLL, заполнения структур данных для установки
соединения с хостом, пересылки данных, разрыва соединения с сервером и
последующего закрытия Winsock.
Свойство Service базового VCL-компонента
CsSocket имеет значение NoService. Компонент CsShopper всегда выполняет
функции FTP-клиента, поэтому в конструкторе TCsShopper.Create свойство
Service получает значение FTP. В остальном протокол FTP использует
стандартные настройки CsSocket — все-таки отличная штука эти компоненты!
Как показано на рис. 6.2, помимо Service CsShopper содержит 10 других
свойств: Access, AddrType,
Asynchronous, Debug,
HomeServer, LogOn, Password, Protocol,
SockType
и UserName.
Рис. 6.2. Свойства CsShopper в инспекторе
объектов Delphi 3
Свойство Asynchronous определяет
режим работы CsShopper — блокирующий или асинхронный . Хотя данное свойство
не относится к протоколу FTP, выбор режима может повлиять на скорость пересылки
данных, надежность приложения и его гибкость. Например, когда CsShopper
работает в асинхронном режиме (то есть свойство Asynchronous равно
TRUE), пользователь может прервать чересчур затянувшуюся пересылку
файла. В блокирующем режиме такая возможность отсутствует (впрочем, если
ChShopper написан как многопоточное приложение, то пересылку файла
можно прервать и в блокирующем режиме, но это совсем другая история).
Асинхронный режим устроен несколько сложнее,
поэтому сначала мы посмотрим, как CsShopper работает в блокирующем режиме.
Асинхронный режим будет описан позднее в этой главе.
Самые полезные FTP-команды (в том числе
USER, PASSWORD, RETR и PUT) реализованы в CsShopper в виде свойств. Эти
свойства находятся в public-секции TCsShopper и потому доступны
для пользователей компонента. В блокирующем режиме соответствующие методы
используют процедуру FTPCommand, которая является «сердцем» компонента
CsShopper. FTPCommand представляет собой простейший анализатор,
реализованный в виде большого оператора case. Недостаток изящества
подобной конструкции возмещается ее простотой. В асинхронном режиме CsShopper
использует другой подход.
Полный исходный текст компонента, находящийся
в файле CSSHOPPER.PAS, занимает около 3000 строк, и я не стал включать
его в эту главу. Будут приведены лишь отдельные фрагменты, поясняющие некоторые
аспекты его работы. Для более подробного знакомства вы можете распечатать
полный файл
с CD-ROM.
Организация вывода
Хотя CsShopper и относится к невизуальным
компонентам, время от времени ему приходится взаимодействовать с приложением
пользователя и отображать сообщения, которыми сервер обменивается с клиентом.
Такую возможность предоставляет published-свойство OnInfo класса
TCsShopper (унаследован ное от класса TCsSocket) и private-процедура
InfoEvent. Процедура InfoEvent выглядит следующим образом:
procedure TCsSocket.InfoEvent(Msg : String);
begin
if Assigned(FOnInfoEvent) then
FOnInfoEvent(Self, Msg);
end;
Когда через управляющее соединение отправляется
или принимается сообщение, локальная переменная TempStr в процедуре
FTPCommand задает значение свойства Info, после чего FTPCommand
вызывает процедуру InfoEvent. Внутри InfoEvent проверка Assignеd
возвращает значение True, а процедура CsShopper1Info из приложения
отображает Info.
Чтобы такое взаимодействие между CsShopper
и клиентским приложением стало возможным, я создал процедуру CsShopper1Info
с помощью вкладки Events инспектора объектов. Содержимое окна memLog,
в котором отображаются все эти сообщения, обновляется с каждым событием
FOnInfoEvent. CsShopper1Info содержит следующий фрагмент
кода:
procedure TfrmMain.CsShopper1Info(Sender:
TObject; Msg: String);
begin
memLog.Lines.Add(Msg);
end;
SHOPPER32 за работой
SHOPPER32 — базовое FTP-приложение, созданное
с помощью компонента CsShopper, оно изображено на рис. 6.3. Создайте новый
проект с именем SHOPPER32, вызовите главную форму frmMain и сохраните
в модуле MAIN.PAS содержимое листинга 6.1.
Листинг 6.1. Модуль MAIN.PAS (*
Модуль Main Написан для книги High Performance Delphi Programming - Джон
К.Пенман 1997 За дополнительной информацией и помощью обращайтесь по адресу
info@craiglockhart.com *)
unit main;
interface
uses
Windows, Messages, SysUtils, Classes,
Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, FileCtrl,
ComCtrls, CsSocket,
CsShopper, MkDirFrm, CsFtpMsg, ToolWin,
Registry, ExtCtrls;
Рис. 6.3. Приложение SHOPPER32
type
TfrmMain = class(TForm)
CsShopper1: TCsShopper;
pcShopper: TPageControl;
tsConnect: TTabSheet;
tsOptions: TTabSheet;
tsAbout: TTabSheet;
gbLocal: TGroupBox;
gbRemote: TGroupBox;
gbActions: TGroupBox;
dcbLocal: TDriveComboBox;
dlbLocal: TDirectoryListBox;
flbLocal: TFileListBox;
sbStatus: TStatusBar;
pbDataTransfer: TProgressBar;
lbRemoteFiles: TListBox;
bbtnExit: TBitBtn;
bbtnConnect: TBitBtn;
bbtnAbort: TBitBtn;
gbUserName: TGroupBox;
gbPassword: TGroupBox;
gbDefLocalDir: TGroupBox;
gbDefTextEditor: TGroupBox;
edDefUserName: TEdit;
edDefPassword: TEdit;
edDefLocalDir: TEdit;
edDefTextEditor: TEdit;
bbtnFtpCmds: TBitBtn;
bbtnLocateTxtEditor: TBitBtn;
bbtnLocateDefLocalDir: TBitBtn;
gbMoreActions: TGroupBox;
bbtnRefresh: TBitBtn;
bbtnFTPHelp: TBitBtn;
bbtnSite: TBitBtn;
bbtnNewDir: TBitBtn;
bbtnDelDir: TBitBtn;
bbtnViewFile: TBitBtn;
memLog: TMemo;
rgFileType: TRadioGroup;
bbtnRestart: TBitBtn;
bbtnQuit: TBitBtn;
tsProfiles: TTabSheet;
gbSetProfile: TGroupBox;
gbPrName: TGroupBox;
gbPrHostName: TGroupBox;
gbPrUserName: TGroupBox;
gbPrPassWord: TGroupBox;
gbPrRemDir: TGroupBox;
gbPrLocDir: TGroupBox;
edPrName: TEdit;
edPrHostName: TEdit;
edPrUserName: TEdit;
edPrPassword: TEdit;
edPrRemDir: TEdit;
edPrLocDir: TEdit;
gbPrList: TGroupBox;
lbPrList: TListBox;
bbtnPrNew: TBitBtn;
bbtnPrSave: TBitBtn;
bbtnPrDelete: TBitBtn;
rgFTPMode: TRadioGroup;
sbbtnRetr: TSpeedButton;
sbbtnStor: TSpeedButton;
Panel1: TPanel;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
bbtnStat: TBitBtn;
gbHints: TGroupBox;
cbHints: TCheckBox;
gbFTPOptions: TGroupBox;
BitBtn2: TBitBtn;
rgFileStructure: TRadioGroup;
rgTransfer: TRadioGroup;
bbtnAddNew: TBitBtn;
procedure bbtnConnectClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure bbtnFtpCmdsClick(Sender: TObject);
procedure CsShopper1Info(Sender: TObject;
Msg: String);
procedure CsShopper1UpDateList(Sender:
TObject; List: TStringList);
procedure lbRemoteFilesDblClick(Sender:
TObject);
procedure CsShopper1List(Sender:
TObject; List: TStringList);
procedure bbtnSiteClick(Sender: TObject);
procedure bbtnFTPHelpClick(Sender: TObject);
procedure CsShopper1Busy(Sender:
TObject; BusyFlag: Boolean);
procedure CsShopper1Progress(Sender: TObject;
Position: Integer);
procedure rgFileTypeClick(Sender: TObject);
procedure CsShopper1FileType(Sender: TObject;
FileType: TFileTypes);
procedure CsShopper1Error(Sender: TObject;
Status: TConditions;
Msg: String);
procedure bbtnNewDirClick(Sender: TObject);
procedure bbtnDelDirClick(Sender: TObject);
procedure CsShopper1Connect(Sender: TObject;
sSocket: Integer);
procedure bbtnQuitClick(Sender: TObject);
procedure rgFTPModeClick(Sender: TObject);
procedure bbtnRefreshClick(Sender: TObject);
procedure sbbtnRetrClick(Sender: TObject);
procedure sbbtnStorClick(Sender: TObject);
procedure CsShopper1DataDone(Sender: TObject;
Done: Boolean);
procedure bbtnStatClick(Sender: TObject);
procedure bbtnRestartClick(Sender: TObject);
procedure flbLocalDblClick(Sender: TObject);
procedure lbRemoteFilesClick(Sender: TObject);
procedure flbLocalClick(Sender: TObject);
procedure lbPrListDblClick(Sender: TObject);
procedure bbtnConnectMouseDown(Sender:
TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure bbtnViewFileClick(Sender: TObject);
procedure bbtnAbortClick(Sender: TObject);
procedure bbtnPrSaveClick(Sender: TObject);
procedure bbtnExitClick(Sender: TObject);
procedure lbPrListClick(Sender: TObject);
procedure bbtnPrNewClick(Sender: TObject);
procedure bbtnAddNewClick(Sender: TObject);
procedure edPrNameExit(Sender: TObject);
procedure edPrHostNameExit(Sender: TObject);
procedure edPrUserNameExit(Sender: TObject);
procedure edPrPasswordExit(Sender: TObject);
procedure edPrRemDirExit(Sender: TObject);
procedure edPrLocDirExit(Sender: TObject);
procedure bbtnPrDeleteClick(Sender: TObject);
procedure bbtnLocateDefLocalDirClick(Sender
: TObject);
procedure bbtnLocateTxtEditorClick(Sender:
TObject);
procedure BitBtn2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
HelpCmd : String;
UsedProfile,
UsedQFTP,
NewProfile : Boolean;
OldTransferMode,
OldFileStruct : String;
OldProfiles,
HostNameList,
UsernameList,
PasswordList,
RemoteDirList,
LocalDirList,
CurrentProfiles,
ProfileNameList : TStringList;
NoOfUsers,
LastProfileUsed,
NoProfiles : Integer;
procedure LoadSettings;
procedure SaveOptions;
procedure SaveProfiles;
end;
var
frmMain: TfrmMain;
implementation
uses RMDirFrm, HelpFrm, QuickFTPfrm,
LocateDirFrm, LocateEdFrm;
{$R *.DFM}
const
FtpClientKey = 'Software\High Performance
Delphi\Shopper32';
procedure TfrmMain.LoadSettings;
var
Reg : TRegistry;
Count : Integer;
ProfileName : String;
begin
Reg := TRegistry.Create;
// Считываем имя пользователя по умолчанию
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('UserName') then
edDefUserName.Text := Reg.ReadString('UserName')
else
edDefUserName.Text := 'anonymous';
finally
Reg.CloseKey;
end;
// Считываем пароль по умолчанию
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('Password') then
edDefPassword.Text := Reg.ReadString('Password')
else
edDefPassword.Text := 'guest';
finally
Reg.CloseKey;
end;
// Считываем локальный каталог по умолчанию
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('DefLocalDir') then
edDefLocalDir.Text
:= Reg.ReadString('DefLocalDir')
else
edDefLocalDir.Text := 'C:\';
finally
Reg.CloseKey;
end;
// Считываем редактор, используемый по умолчанию
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('Editor') then
edDefTextEditor.Text
:= Reg.ReadString('Editor')
else
edDefTextEditor.Text := 'NOTEPAD ';
finally
Reg.CloseKey;
end;
// Задаем свойства
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('Asynchronous') then
begin
with CsShopper1 do
begin
Asynchronous
:= Reg.ReadBool('Asynchronous');
if Asynchronous then
rgFTPMode.ItemIndex := 0
else
rgFTPMode.ItemIndex := 1;
end;
end
else
begin
CsShopper1.Asynchronous := FALSE;
rgFTPMode.ItemIndex := 0;
end;
finally
Reg.CloseKey;
end;
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('Hints') then
cbHints.Checked := Reg.ReadBool('Hints')
else
cbHints.Checked := FALSE;
finally
Reg.CloseKey;
end;
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('DTransferMode') then
begin
OldTransferMode
:= Reg.ReadString('DTransferMode');
if UpperCase(OldTransferMode)
= UpperCase(FtpTransferStr[STREAM]) then
begin
CsShopper1.Transfer := STREAM;
rgTransfer.ItemIndex := 0;
end;
if UpperCase(OldTransferMode)
= UpperCase(FtpTransferStr[BLOCK]) then
begin
CsShopper1.Transfer := BLOCK;
rgTransfer.ItemIndex := 1;
end;
if UpperCase(OldTransferMode)
= UpperCase(FtpTransferStr[COMPRESSED]) then
begin
CsShopper1.Transfer := COMPRESSED;
rgTransfer.ItemIndex := 2;
end;
end else
begin
OldTransferMode
:= UpperCase(FtpTransferStr[STREAM]);
CsShopper1.Transfer := STREAM;
rgTransfer.ItemIndex := 0;
end;
finally
Reg.CloseKey;
end;
// Свойство файловой структуры
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('DFileStructure') then
begin
OldFileStruct := Reg.ReadString('DFileStructure');
if UpperCase(OldFileStruct)
= UpperCase(FtpFileStructStr[NOREC]) then
begin
CsShopper1.FileStruct := NOREC;
rgFileStructure.ItemIndex := 0;
end;
if UpperCase(OldFileStruct)
= UpperCase(FtpFileStructStr[REC]) then
begin
CsShopper1.FileStruct := REC;
rgFileStructure.ItemIndex := 1;
end;
if UpperCase(OldFileStruct)
= UpperCase(FtpFileStructStr[PAGE]) then
begin
CsShopper1.FileStruct := PAGE;
rgFileStructure.ItemIndex := 2;
end;
end else
begin
OldFileStruct
:= UpperCase(FtpFileStructStr[NOREC]);
CsShopper1.FileStruct := NOREC;
rgFileStructure.ItemIndex := 0;
end;
finally
Reg.CloseKey;
end;
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('LastProfileUsed') then
LastProfileUsed
:= Reg.ReadInteger('LastProfileUsed')
else
LastProfileUsed := 0;
finally
Reg.CloseKey;
end;
try
Reg.OpenKey(FtpClientKey, TRUE);
if Reg.ValueExists('NoProfiles') then
NoProfiles := Reg.ReadInteger('NoProfiles')
else
NoProfiles := 1;
finally
Reg.CloseKey;
end;
// Список профилей
for Count := 0 to NoProfiles - 1 do
begin
ProfileName := Concat('ProfileName',
IntToStr(Count));
try
Reg.OpenKey(FtpClientKey + '\Profiles' + '\
' + ProfileName, TRUE);
if Reg.ValueExists('ProfileName') then
ProfileNameList.Add(Reg.ReadString
('ProfileName'))
else
ProfileNameList.Add('PROFILE');
OldProfiles.Add(Reg.ReadString('ProfileName'));
if Reg.ValueExists('Host') then
HostNameList.Add(Reg.ReadString('Host'))
else
HostNameList.Add('HOST');
if Reg.ValueExists('User') then
UserNameList.Add(Reg.ReadString('User'))
else
UserNameList.Add('ANONYMOUS');
if Reg.ValueExists('Password') then
PasswordList.Add(Reg.ReadString('Password'))
else
PasswordList.Add('GUEST');
if Reg.ValueExists('RemoteDir') then
RemoteDirList.Add(Reg.ReadString('RemoteDir'))
else
RemoteDirList.Add('\');
if Reg.ValueExists('LocalDir') then
LocalDirList.Add('LocalDir')
else
LocalDirList.Add('\');
finally
Reg.CloseKey;
end;
end; // цикл for
Reg.Free;
lbPrList.Items := ProfileNameList;
lbPrList.ItemIndex := LastProfileUsed;
edPrName.Text
:= ProfileNameList.Strings[lbPrList.ItemIndex];
edPrHostName.Text
:= HostNameList.Strings[lbPrList.ItemIndex];
edPrUserName.Text
:= UserNameList.Strings[lbPrList.ItemIndex];
edPrPassword.Text
:= PasswordList.Strings[lbPrList.ItemIndex];
edPrRemDir.Text
:= RemoteDirList.Strings[lbPrList.ItemIndex];
edPrLocDir.Text
:= LocalDirList.Strings[lbPrList.ItemIndex];
CsShopper1.UserName := edPrUserName.Text;
CsShopper1.Password := edPrPassword.Text;
lbPrList.Refresh;
end;
procedure TfrmMain.SaveProfiles;
var
Reg : TRegistry;
Count : Integer;
ProfileName : String;
begin
Reg := TRegistry.Create;
try
Reg.OpenKey(FtpClientKey, TRUE);
Reg.WriteInteger('LastProfileUsed',
LastProfileUsed);
finally
Reg.CloseKey;
end;
NoProfiles := lbPrList.Items.Count;
try
Reg.OpenKey(FtpClientKey, TRUE);
Reg.WriteInteger('NoProfiles',NoProfiles);
finally
Reg.CloseKey;
end;
for Count := 0 to NoProfiles - 1 do
begin
ProfileName := Concat('ProfileName',
IntToStr(Count));
try
Reg.OpenKey(FtpClientKey + '\Profiles' + '\
' + ProfileName, TRUE);
Reg.WriteString('ProfileName',
lbPrList.Items.Strings[Count]);
Reg.WriteString('ProfileName',
ProfileNameList.Strings[Count]);
Reg.WriteString('Host',
HostNameList.Strings[Count]);
Reg.WriteString('User',
UserNameList.Strings[Count]);
Reg.WriteString('Password',
PasswordList.Strings[Count]);
Reg.WriteString('RemoteDir',
RemoteDirList.Strings[Count]);
Reg.WriteString('LocalDir',
LocalDirList.Strings[Count]);
finally
Reg.CloseKey;
end;
end;
Reg.Free;
end;
procedure TfrmMain.SaveOptions;
var
Reg : TRegistry;
begin
Reg := TRegistry.Create;
// Сохраняем имя пользователя по умолчанию
try
Reg.OpenKey(FtpClientKey, TRUE);
Reg.WriteString('UserName', edDefUserName.Text);
finally
Reg.CloseKey;
end;
// Сохраняем пароль по умолчанию
try
Reg.OpenKey(FtpClientKey, TRUE);
Reg.WriteString('Password', edDefPassword.Text);
finally
Reg.CloseKey;
end;
// Сохраняем локальный каталог по умолчанию
try
Reg.OpenKey(FtpClientKey, TRUE);
Reg.WriteString('DefLocalDir',
edDefLocalDir.Text);
finally
Reg.CloseKey;
end;
// Сохраняем редактор, используемый по умолчанию
try
Reg.OpenKey(FtpClientKey, TRUE);
Reg.WriteString('Editor', edDefTextEditor.Text);
finally
Reg.CloseKey;
end;
try
Reg.OpenKey(FtpClientKey,TRUE);
case rgFTPMode.ItemIndex of
0 : Reg.WriteBool('Asynchronous',TRUE);
1 : Reg.WriteBool('Asynchronous',FALSE);
end;
finally
Reg.CloseKey;
end;
try
Reg.OpenKey(FtpClientKey, TRUE);
if cbHints.Checked then
Reg.WriteBool('Hints',TRUE)
else
Reg.WriteBool('Hints',FALSE);
finally
Reg.CloseKey;
end;
try
Reg.OpenKey(FtpClientKey,TRUE);
case rgTransfer.ItemIndex of
0 :Reg.WriteString('DTransferMode',
FtpTransferStr[STREAM]);
1 :Reg.WriteString('DTransferMode',
FtpTransferStr[BLOCK]);
2 :Reg.WriteString('DTransferMode',
FtpTransferStr[COMPRESSED]);
end;
finally
Reg.CloseKey;
end;
try
Reg.OpenKey(FtpClientKey,TRUE);
case rgFileStructure.ItemIndex of
0 :Reg.WriteString('DFileStructure',
FtpFileStructStr[NOREC]);
1 :Reg.WriteString('DFileStructure',
FtpFileStructStr[REC]);
2 :Reg.WriteString('DFileStructure',
FtpFileStructStr[PAGE]);
end;
finally
Reg.CloseKey;
end;
Reg.Free;
end;
procedure TfrmMain.bbtnConnectClick(Sender:
TObject);
begin
if (not UsedQFtp) and (not UsedProfile) then
begin
with CsShopper1 do
begin
HostName := HomeServer;
if Status = Success then
Start;
end;
end else
if UsedQFtp then
CsShopper1.Start
else
if UsedProfile then
begin
with CsShopper1 do
begin
UserName := edPrUserName.Text;
Password := edPrPassword.Text;
RemoteDir:= edPrRemDir.Text;
LocalDir := edPrLocDir.Text;
EditName := edDefTextEditor.Text;
HostName := edPrHostName.Text;
if Status = Success then
Start;
end;
end;
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
bbtnQuit.Enabled := FALSE;
bbtnRefresh.Enabled := FALSE;
bbtnViewFile.Enabled := FALSE;
bbtnFtpCmds.Enabled := FALSE;
bbtnAbort.Enabled := FALSE;
rgFileType.Enabled := FALSE;
gbMoreActions.Visible := FALSE;
pbDataTransfer.Visible := FALSE;
sbbtnRetr.Enabled := FALSE;
sbbtnStor.Enabled := FALSE;
OldProfiles := TStringList.Create;
ProfileNameList := TStringList.Create;
HostNameList := TStringList.Create;
UserNameList := TStringList.Create;
PasswordList := TStringList.Create;
RemoteDirList := TStringList.Create;
LocalDirList := TStringList.Create;
LoadSettings;
if CsShopper1.Asynchronous then
begin
sbStatus.Panels[2].Text := Concat('Mode : ',
'Asynchronous');
rgFTPMode.ItemIndex := 0;
end
else
begin
sbStatus.Panels[2].Text := Concat('Mode : ',
'Non-Asynchronous');
rgFTPMode.ItemIndex := 1;
end;
sbStatus.Panels[0].Text
:= Concat('Local Host : ',
CsShopper1.LocalName);
sbStatus.Panels[3].Text
:= Concat('Status : ', 'Idle');
pcShopper.ActivePage := tsProfiles;
UpDate;
end;
procedure TfrmMain.bbtnFtpCmdsClick(Sender:
TObject);
begin
gbMoreActions.Visible
:= not gbMoreActions.Visible;
if gbMoreActions.Visible then
begin
bbtnFtpCmds.Hint := 'Click here to
close the panel of FTP commands';
bbtnFtpCmds.Caption := 'Close';
end
else
begin
bbtnFtpCmds.Hint := 'Click here to
get more FTP commands';
bbtnFtpCmds.Caption := 'FTP Cmds';
end;
end;
procedure TfrmMain.CsShopper1Info(Sender: TObject;
Msg: String);
begin
memLog.Lines.Add(Msg);
end;
procedure TfrmMain.CsShopper1UpDateList(Sender:
TObject;
List: TStringList);
begin
LbRemoteFiles.Items := List;
lbRemoteFiles.UpDate;
gbRemote.Caption := Concat('Files on ',
CsShopper1.HostName);
sbStatus.Panels[1].Text := Concat('Remote Host :
',CsShopper1.HostName);
end;
procedure TfrmMain.lbRemoteFilesDblClick
(Sender: TObject);
begin
pbDataTransfer.Visible := TRUE;
if lbRemoteFiles.ItemIndex <> -1 then
CsShopper1.Get := lbRemoteFiles.Items.Strings
[lbRemoteFiles.ItemIndex]
else
pbDataTransfer.Visible := FALSE;
end;
procedure TfrmMain.CsShopper1List
(Sender: TObject; List: TStringList);
begin
lbRemoteFiles.Clear;
lbRemoteFiles.Items := List;
lbRemoteFiles.UpDate;
gbRemote.Caption := CsShopper1.RemoteDir;
end;
procedure TfrmMain.bbtnSiteClick(Sender: TObject);
begin
CsShopper1.SiteFtp;
end;
procedure TfrmMain.bbtnFTPHelpClick(Sender:
TObject);
var
Counter : Integer;
begin
frmHelp := TfrmHelp.Create(Application);
for Counter := SFtpUser to SFtpNoop do
frmHelp.lbHelpFtpCmds.Items.Add
(LoadStr(Counter));
frmHelp.ShowModal;
CsShopper1.FtpHelp := HelpCmd;
HelpFtpCmdList.Free;
frmHelp.Free;
end;
procedure TfrmMain.CsShopper1Busy
(Sender: TObject; BusyFlag: Boolean);
begin
if BusyFlag then
begin
lbRemoteFiles.Enabled := FALSE;
sbStatus.Panels[3].Text
:= Concat('Status : ','Busy');
end else
begin
lbRemoteFiles.Enabled := TRUE;
sbStatus.Panels[3].Text
:= Concat('Status : ','Idle');
end;
Update;
end;
procedure TfrmMain.CsShopper1Progress
(Sender: TObject; Position: Integer);
begin
pbDataTransfer.Position := Position;
pbDataTransfer.UpDate;
end;
procedure TfrmMain.rgFileTypeClick
(Sender: TObject);
begin
with CsShopper1 do
case rgFileType.ItemIndex of
0 : FileType := ASCII;
1 : FileType := IMAGE;
2 : FileType := AUTO;
end;
end;
procedure TfrmMain.CsShopper1FileType
(Sender: TObject;
FileType: TFileTypes);
begin
case FileType of
ASCII : rgFileType.ItemIndex := 0;
IMAGE : rgFileType.ItemIndex := 1;
AUTO : rgFileType.ItemIndex := 2;
end;
end;
procedure TfrmMain.CsShopper1Error
(Sender: TObject; Status: TConditions;
Msg: String);
begin
memLog.Lines.Add(Msg);
end;
procedure TfrmMain.bbtnNewDirClick
(Sender: TObject);
begin
frmMkNewDir := TfrmMkNewDir.Create(Application);
frmMkNewDir.ShowModal;
if Length(NewDirName) > 0 then
CsShopper1.MkDirName := NewDirName;
frmMkNewDir.Free;
end;
procedure TfrmMain.bbtnDelDirClick(Sender:
TObject);
begin
if lbRemoteFiles.ItemIndex <> -1 then
CsShopper1.RmDirName :=
emoteFiles.Items.Strings[lbRemoteFiles.ItemIndex];
CsShopper1.FilesList;
end;
procedure TfrmMain.CsShopper1Connect(Sender:
TObject; sSocket: Integer);
begin
bbtnQuit.Enabled := TRUE;
bbtnRefresh.Enabled := TRUE;
bbtnViewFile.Enabled := TRUE;
bbtnFtpCmds.Enabled := TRUE;
rgFileType.Enabled := TRUE;
if rgFTPMode.ItemIndex = 1 then
begin
sbbtnRetr.Enabled := TRUE;
sbbtnStor.Enabled := TRUE;
end
else
begin
sbbtnRetr.Enabled := FALSE;
sbbtnStor.Enabled := FALSE;
end;
bbtnConnect.Enabled := FALSE;
bbtnExit.Enabled := FALSE;
rgFTPMode.Enabled := FALSE;
gbRemote.Caption
:= 'Remote : ' + CsShopper1.RemoteDir;
sbStatus.Panels[1].Text
:= 'Remote Host : ' + CsShopper1.HostName;
sbStatus.Panels[3].Text := 'Status : Connected';
Update;
end;
procedure TfrmMain.bbtnQuitClick(Sender: TObject);
begin
bbtnQuit.Enabled := FALSE;
bbtnRefresh.Enabled := FALSE;
bbtnViewFile.Enabled := FALSE;
bbtnFtpCmds.Enabled := FALSE;
bbtnAbort.Enabled := FALSE;
rgFileType.Enabled := FALSE;
sbbtnRetr.Enabled := FALSE;
sbbtnStor.Enabled := FALSE;
gbMoreActions.Visible := FALSE;
pbDataTransfer.Visible := FALSE;
bbtnConnect.Enabled := TRUE;
bbtnExit.Enabled := TRUE;
rgFTPMode.Enabled := TRUE;
with sbStatus do
begin
Panels[1].Text := 'Remote Host : ';
Panels[3].Text := 'Status : Idle';
end;
lbRemoteFiles.Clear;
Update;
CsShopper1.Finish;
end;
(*
procedure TfrmMain.Exit1Click(Sender: TObject);
begin
Close;
end;
*)
procedure TfrmMain.rgFTPModeClick(Sender:
TObject);
begin
if rgFTPMode.ItemIndex = 0 then
begin
CsShopper1.Asynchronous := TRUE;
sbStatus.Panels[2].Text := 'Mode : ' +
'Asynchronous';
sbbtnRetr.Enabled := FALSE;
sbbtnStor.Enabled := FALSE;
end
else
begin
CsShopper1.Asynchronous := FALSE;
sbStatus.Panels[2].Text := 'Mode : ' +
'Non-Asynchronous';
sbbtnRetr.Enabled := TRUE;
sbbtnStor.Enabled := TRUE;
end;
sbStatus.Update;
end;
procedure TfrmMain.bbtnRefreshClick(Sender:
TObject);
begin
CsShopper1.FilesList
end;
procedure TfrmMain.sbbtnRetrClick(Sender:
TObject);
begin
pbDataTransfer.Visible := TRUE;
bbtnAbort.Enabled := TRUE;
CsShopper1.MGet;
end;
procedure TfrmMain.sbbtnStorClick(Sender:
TObject);
begin
pbDataTransfer.Visible := TRUE;
bbtnAbort.Enabled := TRUE;
CsShopper1.MPut;
end;
procedure TfrmMain.CsShopper1DataDone(Sender:
TObject; Done: Boolean);
begin
if Done then
begin
pbDataTransfer.Visible := FALSE;
bbtnAbort.Enabled := FALSE
end
else
begin
pbDataTransfer.Visible := TRUE;
bbtnAbort.Enabled := TRUE
end;
pbDataTransfer.Update;
end;
procedure TfrmMain.bbtnStatClick(Sender: TObject);
begin
CsShopper1.Stat;
end;
procedure TfrmMain.bbtnRestartClick(Sender:
TObject);
begin
ShowMessage('Not implemented in this version');
end;
procedure TfrmMain.flbLocalDblClick(Sender:
TObject);
begin
pbDataTransfer.Visible := TRUE;
if flbLocal.ItemIndex <> -1 then
CsShopper1.Put
:= flbLocal.Items.Strings[flbLocal.ItemIndex]
else
pbDataTransfer.Visible := FALSE;
end;
procedure TfrmMain.lbRemoteFilesClick(Sender:
TObject);
begin
CsShopper1.RemoteFiles.Add
(lbRemoteFiles.Items.Strings
[lbRemoteFiles.ItemIndex]);
end;
procedure TfrmMain.flbLocalClick(Sender: TObject);
begin
CsShopper1.LocalFiles.Add
(flbLocal.Items.Strings[flbLocal.ItemIndex]);
end;
procedure TfrmMain.lbPrListDblClick(Sender:
TObject);
begin
UsedProfile := TRUE;
pcShopper.ActivePage := tsConnect;
ActiveControl := bbtnConnect;
bbtnConnect.Click;
end;
procedure TfrmMain.bbtnConnectMouseDown(Sender:
TObject;
Button: TMouseButton; Shift: TShiftState; X, Y:
Integer);
begin
if Button = mbRight then // Выполняем
упрощенный ftp
begin
UsedQFtp := TRUE;
UsedProfile := FALSE;
frmQuickFtp := TfrmQuickFTP.Create(Application);
frmQuickFtp.ShowModal;
with CsShopper1 do
begin
UserName := frmQuickFtp.edUserName.Text;
Password := frmQuickFtp.edPassword.Text;
HostName := frmQuickFtp.edHostName.Text;
end;
frmQuickFtp.Free;
ActiveControl := bbtnConnect;
bbtnConnect.Click;
end else
UsedQFtp := FALSE;
end;
procedure TfrmMain.bbtnViewFileClick(Sender:
TObject);
begin
if lbRemoteFiles.ItemIndex <> -1 then
CsShopper1.View := lbRemoteFiles.Items.Strings
[lbRemoteFiles.ItemIndex];
end;
procedure TfrmMain.bbtnAbortClick(Sender:
TObject);
begin
CsShopper1.Abort;
bbtnAbort.Enabled := FALSE;
end;
procedure TfrmMain.bbtnPrSaveClick(Sender:
TObject);
begin
SaveProfiles;
end;
procedure TfrmMain.bbtnExitClick(Sender: TObject);
begin
OldProfiles.Free;
ProfileNameList.Free;
HostNameList.Free;
UserNameList.Free;
PasswordList.Free;
RemoteDirList.Free;
LocalDirList.Free;
end;
procedure TfrmMain.lbPrListClick(Sender: TObject);
begin
if lbPrList.ItemIndex <> -1 then
begin
LastProfileUsed := lbPrList.ItemIndex;
edPrName.Text
:= ProfileNameList.Strings[LastProfileUsed];
edPrHostName.Text
:= HostNameList.Strings[LastProfileUsed];
edPrUserName.Text
:= UserNameList.Strings[LastProfileUsed];
edPrPassword.Text
:= PasswordList.Strings[LastProfileUsed];
edPrRemDir.Text
:= RemoteDirList.Strings[LastProfileUsed];
edPrLocDir.Text
:= LocalDirList.Strings[LastProfileUsed];
Update;
end;
end;
procedure TfrmMain.bbtnPrNewClick(Sender:
TObject);
begin
NewProfile := TRUE;
edPrName.Text := '';
edPrHostName.Text := '';
edPrUserName.Text := edDefUserName.Text;
edPrPassword.Text := edDefPassword.Text;
edPrLocDir.Text := edDefLocalDir.Text;
edPrRemDir.Text := '\';
lbPrList.Visible := FALSE;
end;
procedure TfrmMain.bbtnAddNewClick(Sender:
TObject);
begin
ProfileNameList.Add(edPrName.Text);
HostNameList.Add(edPrHostName.Text);
UserNameList.Add(edPrUserName.Text);
PasswordList.Add(edPrPassword.Text);
RemoteDirList.Add(edPrRemDir.Text);
LocalDirList.Add(edPrLocDir.Text);
lbPrList.Items.Add(edPrName.Text);
lbPrList.Visible := TRUE;
lbPrList.refresh;
NewProfile := FALSE;
end;
procedure TfrmMain.edPrNameExit(Sender:
TObject);
begin
if (edPrName.Modified) and (not NewProfile)
then
begin
lbPrList.Items.Strings[lbPrList.ItemIndex]
:= edPrName.Text;
lbPrList.Refresh;
ProfileNameList.Strings[lbPrList.ItemIndex]
:= edPrName.Text;
end;
end;
procedure TfrmMain.edPrHostNameExit(Sender:
TObject);
begin
if (edPrHostName.Modified) and (not NewProfile)
then
HostNameList.Strings[lbPrList.ItemIndex]
:= edPrHostName.Text;
end;
procedure TfrmMain.edPrUserNameExit(Sender:
TObject);
begin
if (edPrUserName.Modified) and (not NewProfile)
then
UserNameList.Strings[lbPrList.ItemIndex]
:= edPrUserName.Text;
end;
procedure TfrmMain.edPrPasswordExit(Sender:
TObject);
begin
if (edPrPassword.Modified) and (not NewProfile)
then
PasswordList.Strings[lbPrList.ItemIndex]
:= edPrPassword.Text;
end;
procedure TfrmMain.edPrRemDirExit(Sender: TObject);
begin
if (edPrRemDir.Modified) and (not NewProfile) then
RemoteDirList.Strings[lbPrList.ItemIndex]
:= edPrRemDir.Text;
end;
procedure TfrmMain.edPrLocDirExit(Sender: TObject);
begin
if (edPrLocDir.Modified) and (not NewProfile)
then
LocalDirList.Strings[lbPrList.ItemIndex]
:= edPrLocDir.Text;
end;
procedure TfrmMain.bbtnPrDeleteClick(Sender:
TObject);
var
Reg : TRegistry;
Profile : String;
begin
Reg := TRegistry.Create;
Profile := Concat('ProfileName',IntToStr
(lbPrList.ItemIndex));
if Reg.DeleteKey(FtpClientKey + '\Profiles\' +
Profile) then
begin
ProfileNameList.Delete(lbPrList.ItemIndex);
HostNameList.Delete(lbPrList.ItemIndex);
UserNameList.Delete(lbPrList.ItemIndex);
PasswordList.Delete(lbPrList.ItemIndex);
RemoteDirList.Delete(lbPrList.ItemIndex);
LocalDirList.Delete(lbPrList.ItemIndex);
lbPrList.Items.Delete(lbPrList.ItemIndex);
edPrName.Clear;
edPrHostName.Clear;
edPrUserName.Clear;
edPrRemDir.Clear;
edPrLocDir.Clear;
NoProfiles := lbPrList.Items.Count;
lbPrList.Refresh;
end;
Reg.Free;
end;
procedure TfrmMain.bbtnLocateDefLocalDirClick
(Sender: TObject);
begin
frmLocateDir := TfrmLocateDir.Create(Application);
frmLocateDir.ShowModal;
edDefLocalDir.Text := frmLocateDir.LocateDir;
frmLocateDir.Free;
end;
procedure TfrmMain.bbtnLocateTxtEditorClick(Sender:
TObject);
begin
frmLocateEditor := TfrmLocateEditor.Create
(Application);
frmLocateEditor.ShowModal;
edDefTextEditor.Text
:= frmLocateEditor.EditorPath;
frmLocateEditor.Free;
end;
procedure TfrmMain.BitBtn2Click(Sender:
TObject);
begin
SaveOptions;
end;
end.
Не забудьте предварительно включить CsSocket
и CsShopper в палитру компонентов. Поместите компонент CsShopper на главную
форму. Создайте на форме кнопку для каждой команды FTP. Например, кнопка
Connect вызывает процедуру
CsShopper1.Start:
procedure TfrmMain.bbtnConnectClick(Sender:
TObject); begin
if (not UsedQFtp) and (not UsedProfile)
then begin
with CsShopper1 do
begin
HostName := HomeServer;
if Status = Success then
Start;
end;
end else
if UsedQFtp then
CsShopper1.Start
else
if UsedProfile then
begin
with CsShopper1 do
begin
UserName := edPrUserName.Text;
Password := edPrPassword.Text;
RemoteDir:= edPrRemDir.Text;
LocalDir := edPrLocDir.Text;
EditName := edDefTextEditor.Text;
HostName := edPrHostName.Text;
if Status = Success then
Start;
end;
end;
end;
Профили SHOPPER32
Перед тем как подключаться к FTP-серверу
с помощью программы SHOPPER32, вы должны создать на вкладке Profiles некий
«профиль», включающий имя FTP-сервера, а также пользовательское имя и пароль
для регистрации (см. рис. 6.4).
Профили сохраняются в системном реестре
Windows и извлекаются из него перед регистрацией, чтобы вам не пришлось
всякий раз вводить информацию для доступа к FTP-серверу.
Чтобы добавить новый профиль, нажмите кнопку
New; при этом стирается содержимое всех текстовых полей на вкладке Profiles.
Затем введите имя профиля, имя FTP-сервера, имя пользователя и пароль в
текстовых полях edPrName, edPrHostName, edPrUserName
и edPrPassword соответственно. Для анонимной регистрации следует
ввести в поле edPrUserName строку anonymous, а в поле edPrPassword
— ваш адрес электронной почты.
Рис. 6.4. Типичный вид профиля на вкладке
Profiles
Нажмите кнопку Add, чтобы внести профиль
в список, и затем сохраните новые данные в реестре кнопкой Save. Если потребуется
удалить профиль из реестра, выделите его имя в списке Profiles и нажмите
кнопку Delete. Чтобы подключиться к FTP-серверу, щелкните на имени профиля
в списке Profiles, перейдите на вкладку Connect и нажмите кнопку Connect.
Существует и другой, более удобный способ — дважды щелкнуть на имени профиля
в списке. При этом автоматически активизируется вкладка Connect, и на ней
нажимается кнопка Connect, как показано в следующем фрагменте обработчика
события OnDblClick для списка
lbPrList:
procedure TfrmMain.lbPrListDblClick(Sender:
TObject);
begin
UsedProfile := TRUE;
pcShopper.ActivePage := tsConnect; ActiveControl
:= bbtnConnect; bbtnConnect.Click;
end;
Чтобы процесс регистрации стал еще проще,
мы сохраняем информацию о локальном и удаленном каталогах в текстовых полях
edPrLocDir и edPrRemDir соответственно. CsShopper пользуется
этой информацией для автоматиче ского, не требующего вмешательства пользователя,
перехода к нужному каталогу.
Чтобы обратиться к редко используемому
FTP-серверу, для которого нет смысла заводить специальный профиль, активизируйте
кнопку Connect (на вкладке Connect) и щелкните на ней правой кнопкой мыши
— на экране появится диалоговое окно Quick FTP. В нем следует ввести имя
пользователя и пароль. Значения по умолчанию берутся с вкладки Options.
Если они окажутся подходящими, вы сразу же начинаете сеанс работы кнопкой
OK.
Замечание
Для получения доступа к некоторым FTP-серверам
и выполнения некоторых FTP-команд (например, удаления каталога командой
RMD) необходимо ввести информацию об используемом ресурсе (она посылается
серверу командой ACCT). Если вы хотите работать с таким сервером, придется
добавить на вкладку Profiles дополнительное текстовое поле и изменить компонент
CsShopper для посылки команды ACCT с соответствующей информацией.
Подключение
Пользуясь введенной информацией, метод CsShopper.Start
вызывает GetHost, чтобы открыть соединение с удаленным хостом. Если
вызов функции завершится неудачно, WSAErrorMsg отображает возможную
причину неудачи и присваивает Status значение Failure. В
противном случае Status присваивается значение Success. При
успешной установке соединения CsShopper вызывает процедуру события ConnEvent
(унаследованную от CsSocket), чтобы сообщить SHOPPER32 о необходимости
изменения состояния кнопок. Например, кнопка Quit блокируется до момента
установления соединения, а затем становится доступной. Start вызывает
FTPCommand для посылки команд USER, PASS, SYST и PWD (именно в таком
порядке) с соответствующими аргументами. Затем Start устанавливает
соединение данных (data connection) для пересылки списка каталогов и файлов
удаленного хоста, при этом порт данных для соединения задается функцией
GetPort.
Чтобы получить список каталогов, Start
посылает команду LIST с помощью FTPCommand. Результат сохраняется,
а последующий вызов Decode анализирует полученные данные и ищет
в них информацию о каталогах и файлах.
Замечание
Механизм анализа несложен, однако описание
каталогов и файлов на разных системах может выглядеть по-разному. Анализатор
CsShopper работает с серверами, использующими Unix и Unix-подобные системы.
Для других операционных систем он иногда выдает неверную информацию о каталогах.
Decode сравнивает первый символ
каждой строки файла FTPFILE.TMP с «d» (для каталогов) или два начальных
символа — с «-r» (для файлов). Если будет найден символ «d», Decode
удаляет его, проверяет оставшуюся часть строки и преобразует ее в знакомый
формат \ddd. Обратная косая черта сообщает SHOPPER32 о том, что строка
содержит имя каталога. Аналогично в случае файлов Decode удаляет
символы «-r» и ищет в строке имя, время, дату и размер файла, выделяя их
в подстроки. Затем эти составные части переставляются так, чтобы получившаяся
строка подходила для просмотра в окне списка SHOPPER32 (см. рис. 6.5).
Метод FRemFiles.Add, используемый
внутри Decode, читает каждую сформатированную строку и заносит ее
в FRemFiles. Свойство FRemFiles представляет собой список
строк, производный от класса TStringList и созданный в конструкторе
TCsShopper.Create.
После того как процедура Decode
завершит построение списка, CsShopper передает FRemFiles процедуре
TCsShopper.ChangeList, вызывающей обработчик
OnList:
procedure TCsShopper.ChangeList(List :
TStringList);
begin
if Assigned(FUpDateList) then
FUpDateList(Self, List);
end;
Рис. 6.5. Отображение файлов и каталогов
в SHOPPER32
Обработчик события OnList в программе
SHOPPER32 обновляет содержимое списка lbRemoteFiles:
procedure TfrmMain.CsShopper1List(Sender:
TObject; List:TStringList);
begin
lbRemoteFiles.Items := List;
lbRemoteFiles.UpDate;
gbRemote.Caption := CsShopper1.RemoteDir;
end;
Закрываем соединение
Для завершения работы с FTP-сервером необходимо
лишь разорвать соединение командой QUIT. Нажатие кнопки Quit приводит к
вызову CsShopper1.Finish и завершению сеанса:
procedure TfrmMain.bbtnQuitClick(Sender: TObject);
begin
bbtnQuit.Enabled := FALSE;
bbtnRefresh.Enabled := FALSE;
bbtnViewFile.Enabled := FALSE;
bbtnFtpCmds.Enabled := FALSE;
bbtnAbort.Enabled := FALSE;
rgFileType.Enabled := FALSE;
sbbtnRetr.Enabled := FALSE;
sbbtnStor.Enabled := FALSE;
gbMoreActions.Visible := FALSE;
pbDataTransfer.Visible := FALSE;
bbtnConnect.Enabled := TRUE;
bbtnExit.Enabled := TRUE;
with sbStatus do
begin
Panels[1].Text := 'Remote Host : ';
Panels[3].Text := 'Status : Idle';
end;
lbRemoteFiles.Clear;
CsShopper1.Finish;
Update;
end;
Прием и передача
файлов
Прием и передача могут осуществляться как
по отдельности, так и пакетами, состоящими из нескольких файлов, Сначала
мы рассмотрим пересылку отдельных файлов. Она начинается двойным щелчком
на имени принимаемого или передаваемого файла в списке.
Ключевым моментом при этом является создание
нового события. После того как вы поместите список lbRemoteFiles
на вкладку Connect, создайте обработчик для его события ObDblClick
на вкладке Events инспектора объектов. Это событие обрабатывается процедурой
TfrmMain.lbRemoteFilesDblClick. Как показано в следующем фрагменте,
в результате имя файла присваивается свойству
CsShopper.Get:
procedure TfrmMain.lbRemoteFilesDblClick(Sender:
TObject); begin
pbDataTransfer.Visible := TRUE;
if lbRemoteFiles.ItemIndex <> -1 then
CsShopper1.Get := lbRemoteFiles.Items.Strings [lbRemoteFiles.ItemIndex]
else
pbDataTransfer.Visible := FALSE; end;
Внутри компонента CsShopper свойство Get
передает имя файла в виде параметра Name процедуре Retrieve.
Чтобы обеспечить правильную пересылку и сохранение файла, SetUpFileTransfer
проверяет расширение файла. Для двоичных файлов (например, EXE, DLL и ZIP)
SetUpFileTransfer приказывает FTP Command выдать команду
TYPE IMAGE, в результате чего сервер будет пересылать файл в виде непрерывного
потока байтов. Для недвоичных файлов SetUp FileTransfer выдает команду
TYPE A. После того как FTP-сервер подтвердит получение команды TYPE, SetUpFileTransfer
через FTPCommand посылает команду RETR имя_файла.
Изменение каталогов
для пересылки файлов
Если двойной щелчок был сделан на имени каталога
(например, \DELPHI), то вместо пересылки SetUpFileTransfer
вызывает ChangeDir, чтобы обработать переход к другому каталогу.
ChangeDir в свою очередь вызывает процедуру FTP Command,
которая посылает FTP-серверу команду CWD имя_каталога (скажем, CWD
\DELPHI). Если сервер принимает команду, он возвращает код ответа 250.
Затем ChangeDir посылает команду LIST (тоже через FTPCommand),
чтобы обновить содержимое списка файлов хоста. Наконец, Decode заполняет
список содержимым нового каталога.
Передача файлов
С точки зрения внутренней логики процесс передачи
файлов похож на их прием. Свойство CsShopper.Put выполняет передачу
с помощью метода PutFile. Чтобы упростить передачу файла от клиента
к серверу, я создал на главной форме несколько списков, производных от
компонентов с вкладки Windows 3.1 палитры: dcbLocal — от TDriveComboBox,
dlbLocal — от TDirectoryListBox и flbLocal — от TFileListBox.
Все эти списки синхронизированы друг с
другом. При выборе в dcbLocal другого дискового устройства немедленно
изменяется содержимое dlbLocal и flb Local. Как и в случае
списка lbRemoteFiles, я воспользовался вкладкой Events инспектора
объектов и создал новый обработчик события OnDblClick — Tfrm
Main.flbLocalDblClick — для двойного щелчка на имени файла в списке
flbLocal. Таким образом, двойной щелчок на имени передаваемого файла
вызывает TfrmMain.flbLocalDblClick, в результате чего имя файла
назначается свойству
CsShopper1.Put.
Пересылка нескольких
файлов
Второй способ позволяет переслать сразу несколько
файлов (пакет). Перед тем как начинать прием, мы выделяем файлы в списке
lbRemoteFiles, щелкая на их именах. При этом в обработчике TfrmMain.lbRemoteFilesClick
имена файлов заносятся в строковый список RemoteFiles. Это демонстрирует
следующий фрагмент кода:
procedure TfrmMain.lbRemoteFilesClick
(Sender: TObject);
begin
CsShopper1.RemoteFiles.Add
(lbRemoteFiles.Items.Strings
[lbRemoteFiles.ItemIndex]);
end;
На рис. 6.6 видно несколько файлов, выделенных
в каталоге удаленного хоста и готовых к приему. После того как будут выделены
все принимаемые файлы, начинайте пересылку с помощью кнопки , расположенной
вверху рядом со списком lbRemoteFiles. При этом будет вызван метод CsShopper.MGet.
Соответствующий код выглядит так:
procedure TfrmMain.sbbtnRetrClick(Sender: TObject);
begin
pbDataTransfer.Visible := TRUE;
bbtnAbort.Enabled := TRUE;
CsShopper1.MGet;
end;
Рис. 6.6. Выделенные файлы готовы к пакетному
приему
Однако для того, чтобы описанная схема
работала, нам придется изменить два свойства списка lbRemoteFiles
в инспекторе объектов: во-первых, измените значение ExtendedSelect
с FALSE на TRUE, а во-вторых, измените значение MultiSelect
также с FALSE на TRUE. Если теперь щелкнуть на имени файла
в списке lbRemoteFiles, оно заносится в строковый список CsShopper1.RemoteFiles
(относящийся к типу TStringList). Аналогично в случае пакетной передачи
вам придется изменить те же два свойства для списка flbLocal.
Замечание
Учтите, что возможность пакетной пересылки
отсутствует в асинхронном режиме — это обусловлено трудностями с синхронизацией
файловых операций.
Асинхронная пересылка
файлов
Познакомившись с протоколом FTP в блокирующем
(синхронном) режиме, кратко рассмотрим работу CsShopper в асинхронном режиме.
Поскольку процесс регистрации на FTP-сервере подробно описан выше, наше
основное внимание будет сосредоточено на пересылке, и особенно — на асинхронном
приеме файла с FTP-сервера.
Перед тем как подключаться к FTP-серверу
в асинхронном режиме, следует установить переключатель Asynchronous в групповом
поле FTP Mode вкладки Options. Этот переключатель управляет режимом всего
соединения; после того как SHOPPER32 подключится к FTP-серверу, групповое
поле FTP Mode блокируется до окончания сеанса.
Процесс выбора принимаемого файла в асинхронном
режиме происходит так же, как и в блокирующем режиме; другими словами,
перед вызовом Retrieve мы присваиваем имя файла свойству Get.
Отличия начинаются внутри Retrieve. Определив тип файла, мы присваиваем
флагу состояния FFtpCmd значение FTP_TYPEI и тем самым приказываем
серверу переслать файл как непрерывный поток байтов. Команда TYPE передается
через процедуру SendFtpCmd.
Когда Winsock получает событие сокета FD_READ,
которое происходит в результате ответа FTP-сервера на команду TYPE, он
посылает процедуре FtpEvent сообщение с описанием события. В FtpEvent
сообщение анализируется на предмет поиска событий FD_READ, FD_WRITE
и FD_CLOSE. Для распознавания события сокета используется оператор
case.
При получении события FD_READ процедура
InfoEvent отправляет все содержимое буфера FRcvBuffer для
вывода в приложении SHOPPER32. В буфере FRcv Buffer, содержащем
код ответа от сервера, ищется символ 4 или 5, свидетель ствующий об ошибке
FTP. Если поиск окажется успешным, FFtpCmd присваивается значение
FTP_FAIL, которое сигнализирует приложению о возникнове нии ошибки.
В противном случае процедура ProcessRecvData
обрабатывает FRcvBuffer и флаг состояния FFtpCmd с использованием
оператора case. Так как FFtpCmd имеет значение FTP_TYPEI,
ProcessRecvData вызывает процедуру ProcessTypeI, в которой
выполняется подробный анализ содержимого FRcvBuffer. Следующий фрагмент
кода показывает, как это делается:
procedure TCsShopper.ProcessTypeI;
begin
case GetReplyCode(FRcvBuffer) of
200 : begin
if Pos('200-',String(FRcvBuffer)) = 0
then // Сервер ждет, пока мы создадим
// соединение данных и пошлем команду USER
begin
ProcessPort;
end;
{ остаток кода пропущен }
end; // case
FillChar(FRcvBuffer, SizeOf(FRcvBuffer),#0);
end;
Если код ответа равен 200, вызывается процедура
ProcessPort, из которой в свою очередь вызывается InitDataConn,
выполняющая четыре задачи:
|
создание
сокета для соединения данных; |
|
вызов
WSAAsyncSelect для создания логического номера окна, позволяю щего
FtpDataEvent перехватывать события сокета, связанные с соедине нием
данных; |
|
вызов
функции Winsock API bind для связывания нового сокета данных; |
|
вызов
listen для перевода сокета данных в состояние «прослушивания» (listening). |
Если в результате вызова InitDataConn
будет создан допустимый сокет данных, ProcessPort создает для соединения
данных уникальный номер порта, который затем передается процедурой SendFtpCmd.
Наконец, флагу состояния FFtpCmd присваивается значение FTP_RETR,
которое сигнализирует CsShopper о том, что следующее событие сокета FD_READ
должно анализироваться в контекс те приема файла.
Когда на управляющем соединении происходит
следующее событие FD_READ (при условии отсутствия ошибок сокета
или отрицательных кодов ответа), вызывается процедура ProcessRecvData,
которая в свою очередь инициирует ProcessGet.
В ProcessGet при получении кода
ответа 200 (признак успеха) создается локальный файл, имя которого совпадает
с именем файла на сервере. В дальнейшем код ответа 150 сигнализирует FTP-клиенту
о том, что сервер приступил к пересылке информации через соединение данных.
Сразу же после того, как FTP-сервер свяжется
с клиентом через соединение данных, Winsock уведомляет об этом процедуру
FtpDataEvent с помощью события FD_ACCEPT. В ветви FD_ACCEPT
оператора case вызывается функция WSAAsyncSelect, которая
инициализирует сокет данных для приема только следующих событий:
FD_READ, FD_WRITE и FD_CLOSE. Следующий фрагмент процедуры
FtpDataEvent показывает, как это делается:
FD_ACCEPT : begin
FStartTime := GetTickCount;
FIntTime := FStartTime;
if FListenSocket <> INVALID_SOCKET then
begin
nLen := SizeOf(TSockAddr);
FDataSocket := accept(FListenSocket,
@FRemoteHost, @nLen);
if FDataSocket = SOCKET_ERROR then
begin
InfoEvent(Concat('Error : ',WSAErrorMsg));
FFtpCmd := FTP_FAIL;
Exit;
end;
nStat := WSAAsyncSelect(FDataSocket, FDataWnd,
DATA_EVENT,
FD_READ or
FD_WRITE or
FD_CLOSE);
if nStat = SOCKET_ERROR then
begin
InfoEvent(Concat('Error : ',WSAErrorMsg));
FFtpCmd := FTP_FAIL;
Exit;
end;
{ остаток кода пропущен }
end;
end;
При приеме первого и последнего пакета
данных через соединение данных Winsock уведомляет FtpDataEvent с
помощью события FD_READ, что приводит к вызову RecvData для
получения и сохранения поступающих данных в локальном файле. После завершения
пересылки FTP-сервер закрывает соединение данных со своей стороны, заставляя
Winsock послать сообщение FD_CLOSE. На этом пересылку файла логично
было бы завершить, но иногда в сокете данных FTP-клиента все еще остаются
непрочитанные данные. Чтобы избежать потерь информации, мы присваиваем
флагу
FTransferDone значение TRUE. Все сказанное демонстрируется
следующим фрагментом кода из процедуры FtpDataEvent:
FD_CLOSE : begin
FTransferDone := TRUE;
case FFTPCmd of
FTP_RETR,
FTP_LIST,
FTP_VIEW : RecvData;
FTP_STOR : SendData;
end;
end;
Флаг FTransferDone сообщает о необходимости
продолжить чтение оставшихся данных сокета в цикле while, как показано
в следующем фрагменте кода процедуры RecvData:
FTP_RETR : begin
{ часть кода пропущена }
if FTransferDone then // Работа с
//FTP-сервером закончена,
// однако необходимо прочитать
// и сохранить данные, оставшиеся
// в сокете данных
begin
Done := FALSE;
while not Done do
begin
BlockWrite(FRetrFile, FDataBuffer, Response);
{ часть кода пропущена }
Response := recv(FDataSocket, FDataBuffer,
SizeOf(FDataBuffer), 0);
if Response = SOCKET_ERROR then
begin
Done := TRUE;
WSAAsyncSelect(FDataSocket,
// Прекратить посылку
FDataWnd, 0, 0); // уведомлений
CloseSocket(FDataSocket);
System.CloseFile(FRetrFile);
ChangeBusy(FALSE);
ChangeDataDone(TRUE);
InfoEvent(Concat('ERROR : ',WSAErrorMsg));
end;
if Response = 0 then // Данных не осталось
begin
{ часть кода пропущена }
Done := TRUE;
WSAAsyncSelect(FDataSocket,
FDataWnd, 0, 0);
CloseSocket(FDataSocket);
System.CloseFile(FRetrFile);
ChangeBusy(FALSE);
ChangeDataDone(TRUE);
GetList;
end;
end;
end else
if Response > 0 then
// FTP-сервер продолжает
// посылать данные,
// их необходимо обработать
begin
BlockWrite(FRetrFile, FDataBuffer, Response);
{ часть кода пропущена }
end;
end;
Передача файла FTP-серверу в асинхронном
режиме выполняется по тому же принципу, что и прием.
Положи на место!
В асинхронном режиме в отличие от блокирующего
можно легко прервать затянувшуюся пересылку файла — достаточно нажать кнопку
Abort на вкладке Connect (обратите внимание на то, что в блокирующем режиме
эта кнопка недоступна). При нажатии кнопки Abort вызывается метод CsShopper.Abort,
который посылает серверу через управляющее соединение команду ABOR. Рассмотрим
следующий фрагмент кода:
procedure TCsShopper.Abort;
begin
ChangeBusy(TRUE);
SendFtpCmd(LoadStr(SFtpAbor));
FFtpCmd := FTP_ABORT;
ChangeBusy(FALSE);
end;
При получении кода ответа 226, означающего
успешную отмену пересылки, CsShopper.ProcessAbort закрывает соединение
данных, а в случае приема файла — стирает локальный файл.
Заключение
FTP-клиент CsShopper — невизуальный компонент.
Он не умеет сохранять и загружать имена хостов, имена пользователей, пароли
и сведения о ресурсах. Все это остается на совести программистов, которые
должны спроектировать эти визуальные средства в соответствии с потребностями
конкретного приложения. Тем не менее приложение SHOPPER32 наглядно показывает,
как легко можно при необходимости организовать сохранение и загрузку профилей.
|