Delphiでお手軽プログラミング

Delphiでお手軽プログラミングメニュー

DelphiでTGPuttyLibを使ってSFTPクライアントを作る

TGPuttyLibを使ってSFTPクライアントを作成します。
以下サンプルでは、ホストへの接続、切断、フォルダ・ファイルの一覧の取得、ホストのカレントディレクトリの移動のみを作成します。

ファイルのダウンロード機能
sftp.DownloadStream(Utf8Encode('ファイル名'), Stream, false);
ファイルのアップロード機能
sftp.UploadStream(Utf8Encode('ファイル名', Stream, false);
ディレクトリ作成機能
sftp.MakeDir(Utf8Encode('ディレクトリ名'));
は作りません。
またSSHトンネル接続(ポートフォワーディング)はTGPuttyLibでは出来ないようです。
OpenSSHを使ってトンネルをつくることはできます。


0.TGPuttyLibのダウンロード

https://github.com/superflexible/TGPuttyLib
から「Code」⇒「Download ZIP」をクリックして「TGPuttyLib-master.zip」ファイルをダウンロードし解凍する。

32Bitで実行ファイルをコンパイルする場合は、
解凍した「TGPuttyLib-master\Win32\Debug」フォルダ内にある「tgputtylib.dll」ファイルを実行ファイルと同じフォルダに入れる必要がある。

64Bitで実行ファイルをコンパイルする場合は、
解凍した「TGPuttyLib-master\Win64\Debug」フォルダ内にある「tgputtylib.dll」ファイルを実行ファイルと同じフォルダに入れる必要がある。


Delphiにコンポーネントをインストールすることは非推奨みたいですが、Delphiにインストールする場合は

  1. DelphiのIDEから「ツール」⇒「オプション」をクリックし、 [環境オプション]⇒[Delphi オプション]⇒[ライブラリ]を選択し、 ライブラリパスに「tgputtylib-master.zipを解凍したフォルダ」を追加する。
  2. 「TGPuttyLib-master\Win32\Debug」フォルダ内にある「tgputtylib.dll」ファイルを パスの通ったフォルダ(例えばc:\windows)にコピーするか、 「TGPuttyLib-master\Win32\Debug」フォルダをWindowsOSのパスに追加する。
  3. 解凍したフォルダ直下にある「TGPuttyLib.dproj」プロジェクトファイルをDelphiのIDEから開いて、 右上ペインのプロジェクトマネージャーから、 TGPuttyLib.bplを右クリックして[インストール]をクリックするとインストールできる。

インストールせずに使用する場合は上記1.を行います。


1.プロジェクトの作成

ファイル⇒新規作成⇒Windows VCL アプリケーション -Delphi をクリックします。
TEditとTListViewとTMemoとTButton×2個をドラッグ&ドロップします。


2.プロジェクトの保存

「ファイル」⇒「すべて保存」を押して、プロジェクトとユニットを保存します。
「新しいフォルダ」ボタンを押して「tgputty」フォルダ等を作成し、ユニットはデフォルトの「Unit1.pas」、プロジェクトもデフォルトの「Project1.dproj」で保存します。
「プロジェクト」⇒「Project1をコンパイル」を押します。
プロジェクトフォルダ内の「win32\degub」フォルダ内に、解凍した「TGPuttyLib-master\Win32\Debug\tgputtylib.dll」ファイルをコピーします。

3.ソースコードの記述

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls
  ,System.DateUtils
  ,tgputtylib, tgputtysftp, Vcl.ComCtrls ;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure ListView1DblClick(Sender: TObject);
  private
    { Private 宣言 }
    sftp:TTGPuttySFTP;
    //ファイル,フォルダの一覧取得時等のコールバック
    function OnListingCallback(const names:Pfxp_names):Boolean;
    //何らかのホストからのメッセージのコールバック
    procedure OnMessageCallback(const Msg:AnsiString;const isstderr:Boolean);
    function OnProgressCallback(const bytescopied:Int64;const isupload:Boolean):Boolean;
    //このイベントは通常発生しない
    function OnGetInputCallback(var cancel:Boolean):AnsiString;
    //ホスト接続時のフィンガープリントのコールバック
    function OnVerifyHostKeyCallback(
      const host:PAnsiChar;
      const port:Integer;
      const fingerprint:PAnsiChar;
      const verificationstatus:Integer;
      var storehostkey:Boolean):Boolean;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  //接続設定
  sftp.HostName :='接続ホスト名またはIPアドレス';
  sftp.Port     :=22;           //ポート番号(通常は22)
  sftp.UserName :='ユーザー名';
  sftp.Password :='パスワード';
  sftp.Keyfile  :=Utf8Encode('必要なら鍵ファイルのフルパス');

  //接続
  sftp.Connect;

  //ディレクトリ内のディレクトリ・ファイルリストの取得
  sftp.ChangeDir(sftp.WorkDir);
  sftp.ListDir('');
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  sftp.Disconnect;
  ListView1.Clear;
end;

procedure TForm1.FormCreate(Sender: TObject);
var c:TListColumn;
begin
  sftp:=TTGPuttySFTP.Create(true);//true:詳細をOn
  sftp.OnListing:=OnListingCallback;
  sftp.OnMessage:=OnMessageCallback;
  sftp.OnProgress:=OnProgressCallback;
  sftp.OnGetInput:=OnGetInputCallback;
  sftp.OnVerifyHostKey:=OnVerifyHostKeyCallback;

  ListView1.ViewStyle:=TViewStyle.vsReport;
  ListView1.RowSelect:=True;
  ListView1.ReadOnly:=True;
  c:=ListView1.columns.Add;
  c.Caption:='種類';
  c.Width:=96;
  c:=ListView1.columns.Add;
  c.Caption:='名前';
  c.Width:=256;
  c:=ListView1.columns.Add;
  c.Caption:='更新日';
  c.Width:=256;
  c:=ListView1.columns.Add;
  c.Caption:='サイズ';
  c.Width:=200;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(sftp);
end;

procedure TForm1.ListView1DblClick(Sender: TObject);
begin
  if ListView1.ItemIndex<0 then exit;

  if ListView1.Items[ListView1.ItemIndex].Caption<>'<DIR>' then exit;

  sftp.ChangeDir(
    Utf8Encode(
      Edit1.text+'/'+ListView1.items[ListView1.ItemIndex].subitems[0]
    )
  );
  sftp.ListDir(
    Utf8Encode(
      Edit1.text+'/'+ListView1.items[ListView1.ItemIndex].subitems[0]
    )
  );

end;

function TForm1.OnGetInputCallback(var cancel: Boolean): AnsiString;
begin
  //このイベントは通常発生しない
  Result:='';
  cancel:=false;
end;

function TForm1.OnListingCallback(const names: Pfxp_names): Boolean;
var i:integer;
begin
  //ファイル,フォルダの一覧取得時等のコールバック

  Edit1.text:=Utf8ToString(sftp.WorkDir);

  ListView1.items.Clear;
  for i := 0 to names.nnames-1 do
  begin
    if Pfxp_name_array(names.names)[i].attrs.permissions and $F000=$4000 then
      ListView1.AddItem('<DIR>',nil) //ディレクトリ
    else
      ListView1.AddItem('',nil);     //ファイル
    //名前
    ListView1.items[ListView1.items.Count-1].SubItems.Add(
      Utf8ToString(Pfxp_name_array(names.names)[i].filename)
    );
    ////タイムスタンプ
    ListView1.items[ListView1.items.Count-1].SubItems.Add(
      DateTimeToStr(
        TTimeZone.Local.ToLocalTime(
          UnixToDateTime(
            Pfxp_name_array(names.names)[i].attrs.mtime
          )
        )
      )
    );
    if Pfxp_name_array(names.names)[i].attrs.permissions and $F000=$4000 then
      ListView1.items[ListView1.items.Count-1].SubItems.Add('')
    else
      ListView1.items[ListView1.items.Count-1].SubItems.Add(
        FormatFloat('#,##0',Pfxp_name_array(names.names)[i].attrs.size)
      );
  end;
  result:=True;
end;

procedure TForm1.OnMessageCallback(const Msg: AnsiString;
  const isstderr: Boolean);
begin
  //何らかのホストからのメッセージのコールバック
  Memo1.Lines.Add(Utf8ToString(Msg));
end;

function TForm1.OnProgressCallback(const bytescopied: Int64;
  const isupload: Boolean): Boolean;
begin
  //ファイルのアップ,ダウンロード時の進捗中のコールバック
  result:=True;
end;

function TForm1.OnVerifyHostKeyCallback(
  const host: PAnsiChar;
  const port: Integer; const fingerprint: PAnsiChar;
  const verificationstatus: Integer; var storehostkey: Boolean): Boolean;
var ret:Integer;
begin
  if verificationstatus=0 then
  begin
    Result:=True;
    exit;
  end;
  ret:=Application.MessageBox(
    PChar(
      'ホスト「'+
      Utf8ToString(AnsiString(host))+':'+
      IntToStr(port)+'」'+sLineBreak+
      'のフィンガープリントは'+#13#10+
      Utf8ToString(AnsiString(fingerprint))+#13#10+
      'です。接続してもよろしいですか?'+#13#10+
      '(本アプリはフィンガープリントをレジストリ等に存しません)'
    ),
    '確認',
    MB_YESNO
  );
  if ret=IDYES then
    Result:=true  //接続許可
  else
    Result:=False;//接続中止
  //フィンガープリントをレジストリに保存しない
  storehostkey:=False;
end;

end.

4.実行する

実行⇒実行を押して実行します。


Button1をクリックすると接続を開始し、フィンガープリントの確認が表示されます。「はい」をクリックすると接続されます。


ディレクトリをダブルクリックすると子ディレクトリに移動して一覧が表示されます。


Button2をクリックすると切断されます。

Copyright 2019 Mam