プラグインを作る

プラグインを作成することで、Sekireiに新たな機能を付加することができます。プラグインによる新たな機能は、既存のSFTPやFTPSの機能と対等です。それは、SFTPやFTPSの機能も又、プラグインと同様に、ここで説明されている手法で実装されているからです。

プラグインを作成するには、IProtocolIBridgeXmlIBridgeUiの3つのインターフェースを実装します。(ごく)大雑把(おおざっぱ)に言えば、IProtocolインターフェースはSekireiのメイン画面に表示されるボタン、IBridgeXmlインターフェースは設定内容の保存と読み出し、IBridgeUiインターフェースは設定画面をそれぞれ受け持ちます。「ブリッジを作る」で実装例として使用したRamBridgeのように設定内容が何も無い場合、設定画面は必要ありません。IBridgeUiインターフェースの実装は省くことができます。設定内容が無いのであればその保存や読み出しも必要無いように思うかもしれませんが、接続情報の存在をSekireiに知らせるために、空の設定が必要です。IBridgeXmlインターフェースの実装は必須です。勿論(もちろん)、メイン画面にボタンが表示されなければ何も始まらないため、これを受け持つIProtocolインターフェースについても実装は必須です。各インターフェースの詳細についてはAPIリファレンスを参照してください。

(なお)、現時点においてSekireiは.NET 8.0の下で動作しますが、将来、利用する.NETのバージョンを引き上げることもあり得ます。そのため、プラグインは.NET Standardを対象に作成することをお勧めします。ちなみに、Sekireiは「自己完結型」アプリケーションとしてビルドされているため、別途、.NET 8.0をインストールする必要はありません。

プラグインの実装

先ずは、実装に際して必要なアセンブリを以下からダウンロードしてください。

ダウンロード

ダウンロード

Kasasagi.Sekirei.Ui.zip

ダウンロードしたZIPファイルを解凍すると、4つのファイルが展開されます。"Kasasagi.DavServer.dll"と"Kasasagi.Sekirei.Ui.dll"がアセンブリファイルです。前述の3つのインターフェースは"Kasasagi.Sekirei.Ui.dll"の中で定義されています。"Kasasagi.Sekirei.Ui.dll"には、引数や返戻値がIBridge型を取るメソッドが含まれるため、それが定義されている"Kasasagi.DavServer.dll"もZIPファイルには含まれています。"Kasasagi.DavServer.xml"と"Kasasagi.Sekirei.Ui.xml"の2つのXMLファイルは、それぞれのAPIドキュメントです。


IProtocol

IProtocolインターフェースは実装の中心となるインターフェースです。IBridgeXmlオブジェクトやIBridgeUiオブジェクトはIProtocolのプロパティとして外部に公開されます。重要なことは、このインターフェースの実装クラスが、publicでなければならないということと、引数の無いデフォルトコンストラクタを持たなければならないということです。実装クラスのインスタンス化はリフレクションによって行われるため、デフォルトコンストラクタは必須です。

このインターフェースを実装するに当たって、難しいことはさほどありません。Titleプロパティでボタンの表題を、Colorプロパティでボタンの色を決定します。ボタンの色は「#」で始まるHTML形式のカラーコードで指定します。これはRGB色の16進表現です。例えば、赤であれば"#FF0000"です。

Xmlプロパティは設定内容の保存と読み出しに使用されます。このプロパティは決してnullであってはなりません。このプロパティがnullを返す場合、そのIProtocol実装クラスは使用されません。

Uiプロパティは設定画面を定義します。設定画面が必要無ければ、このプロパティはnullでも構いません。

GetNameメソッドが返す値は、接続情報の上段に表示される名前です。

多くの場合、ここには権限(Authority)が表示されるものと想定されるため、AuthorityクラスにGetAuthorityという、権限を生成するための静的メソッドが用意されています。特に、IPv6アドレスから権限を生成する際には面倒を回避できます。


IBridgeXml

IBridgeXmlインターフェースは、設定内容をXMLに変換したり、逆にXMLから設定内容を読み出したりするのに使用されます。Sekireiが設定内容をファイルに保存しようとする時、このインターフェースのWriteメソッドが呼び出され、IBridgeオブジェクトがXMLに変換されます。Sekireiが設定内容をファイルから読み出そうとする時には、Readメソッドが呼び出され、XMLがIBridgeオブジェクトに変換されます。

XMLの内容は任意です。開発者にとって都合の良い形式で設定内容を格納できます。Readメソッドの引数やWriteメソッドの返戻値がSystem.Xml.Linq名前空間で定義されたXElement型の値であるため、これを使い慣れていない開発者にとっては少々厄介です。例えば、ホスト名とポート番号を保存するのであれば、Writeメソッドの実装は以下のようになるでしょう。

C#
return new XElement(
    "test",
    new XElement("host", host),
    new XElement("port", port.ToString()));

このコードが生成するXMLは以下のような内容です。

XML
<test>
    <host>192.0.2.1</host>
    <port>21</port>
</test>

設定内容が何も存在しない場合でも、Writeメソッドは決してnullを返してはなりません。このメソッドにとって返戻値のnullは変換の失敗を意味し、その場合、この実装は事実上機能しません。設定内容が無い場合は、以下のように空のXML要素を返さなければなりません。

C#
return new XElement("ram");

これによって、Sekireiは有効な接続情報の存在を認識することができます。

IBridgeXmlインターフェースには一つ、重要なプロパティが含まれます。その実装を一意に識別するためのIdプロパティです。「一意に識別する」とは、仮に複数の異なる実装クラスのWriteメソッドが同じ要素名のXMLを返したとしても、これらを別々の実装のものとして区別できるということです。例えば、IBridgeXmlインターフェースの実装としてRam1とRam2の2つのクラスが存在したとします。もしこれらのクラスのWriteメソッドが、同じ"<ram />"というXMLを返した場合、これを受け取ったSekireiは、このXMLがどちらの設定内容なのかを判別できません。そのため、XMLを受け取ったSekireiは、そのXML要素にid属性を追加し、その値としてIdプロパティの値を設定します。これはあくまで、XMLを受け取ったSekireiが行う処理です。Writeメソッドがこれを行う必要はありません。

XML
<ram id="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" />

Ram1とRam2のIdプロパティの値が異なれば、SekireiはそのXMLがどちらのものなのかを判別することができます。Idプロパティは、その実装がその実装であることを示すための、唯一無二の証明書類なのです。このプロパティはGUIDを返します。「唯一無二の証明書類」としてはおあつらえ向きですが、実装に当たっては注意が必要です。Guid.NewGuidメソッドを使用して動的に値を設定してはなりません。これではクラスがインスタンス化される度に異なるGUIDがIdプロパティに割り当てられてしまい、証明書類としての用を成しません。このプロパティの実装は、以下のように必ずハードコードします。

C#
public Guid Id { get; } = Guid.Parse("{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}");


IBridgeUi

IBridgeUiインターフェースはSekireiに設定画面を提供します。前述の通り、設定内容が存在しなければIProtocol実装クラスのUiプロパティはnullを返すため、IBridgeUiインターフェースの実装は必要ありません。

何かしら設定が存在するのならば、このインターフェースを実装しなければなりません。このインターフェースは、ブリッジから設定画面を生成するEncodeメソッドと、設定画面からブリッジを構築するDecodeメソッドの2つから成ります。ただし、ここで言う設定画面とは、実際に表示される設定画面の「設計図」を意味します。設定画面そのものを直接やり取りする訳ではありません。その設計図は、UiSourceクラスとその子クラスによって組み立てます。IBridgeUiインターフェースを実装するには、先ず、これらのクラスの扱い方を知らなければなりません。

UiSourceクラスの子クラスは、多くが設定画面の入力要素を表すクラス群です。

クラス 入力要素 スクリーンショット
Text 文字列入力用
Integer 整数入力用
Password パスワード入力用
File ファイル選択用
List リスト選択用
Nest 入力要素の入れ子

利用可能な入力要素はこれら6つだけです。整数入力用のテキストボックスは存在しますが、小数入力用のテキストボックスは存在しません。


Encodeメソッド内で実際に入力要素を幾つか作成してみます。以下では文字列入力用、整数入力用、パスワード入力用の各テキストボックスを作成しています。

C#
return new UiSource(
    0xFFFF0000,
    0xFFFF8888,
    new UiSource.Item(false, "Text", new UiSource.Text(text)),
    new UiSource.Item(false, "Integer", new UiSource.Integer(count)),
    new UiSource.Item(false, "Password", new UiSource.Password(password)));

Itemクラスは設定画面の1行分を表すクラスです。設定画面の1行の中には、左側に項目名、右側に入力要素が格納されます。Itemコンストラクタの第2引数には左側の項目名、第3引数には右側の入力要素を指定します。

この例ではItemコンストラクタの第1引数にfalseを指定していますが、この引数にtrueを指定すると、入力要素の背景が赤く表示され、入力が必須であることが示されます。

この入力要素に値を入力すると、背景の色は白に変わります。

UiSourceコンストラクタの第3引数以降には設定画面の各行が指定されています。第1引数と第2引数の16進数はARGB形式の色を表す値です。第1引数は項目名が表示されている見出しの色、第2引数は行が存在しない部分の背景色です。

見出し 見出し 背景 背景


Encodeメソッドの実装で最も厄介(やっかい)なのは、子項目を抱えるNestクラスを使用する時です。このクラスは抽象クラスであるため、使用するには開発者がその継承クラスを実装しなければなりません。実装するのはコンストラクタと、子項目を返すChildrenプロパティの2つだけですが、子項目は親であるParentプロパティが保持する値によって変化し得ます。例えば、親に入力された個数分だけ子項目を返すNest派生クラスを作成する必要があったとします。

親は恐らく整数値を入力するためのIntegerオブジェクトになるでしょう。Childrenプロパティの実装内では先ず、親が保持する整数値を取得します。幸い、入力要素クラスが継承するControlクラスには、保持する値を取得するためのメソッドが用意されています。Integerオブジェクトから値を取り出すのであればGetIntegerメソッドを使用します。このメソッドで取得した値に従って子項目を生成します。Childrenプロパティの実装は以下のようになるはずです。

C#
List<UiSource.Item> items = new List<UiSource.Item>();

if (this.Parent.GetInteger(out int? count))
{
    if (count.HasValue)
    {
        for (int i = 0; i < count.Value; i++)
        {
            items.Add(new UiSource.Item(false, "Sub" + i.ToString(), new UiSource.Text(this.Values[i])));
        }
    }
}

Encodeメソッドの実装では、Nest派生クラスのインスタンスを収めたItemオブジェクトを生成し、それをUiSourceコンストラクタに渡します。

C#
return new UiSource(
    0xFFFF0000,
    0xFFFF8888,
    new UiSource.Item(false, "Text", new UiSource.Text(text)),
    new UiSource.Item(false, "Integer", new IntegerNest(new UiSource.Integer(count), values)),
    new UiSource.Item(false, "Password", new UiSource.Password(password)));

このコードの中のIntegerNestがNest派生クラスです。このクラスのコンストラクタには引数として、親の入力要素と、子項目の初期値が渡されています。


Controlクラスが保有するメソッドは、Decodeメソッドを実装する際にも利用されます。Decodeメソッドでは、入力要素に格納された値を1つ1つ取り出し、それらの値に基づいてブリッジのインスタンスを生成します。

C#
source.Items[index]?.Control?.GetInteger(out int value);

入力内容に間違いがあれば、ブリッジが生成不能であることを示すために、Decodeメソッドはnullを返さなければなりません。このメソッドがnullを返すと、設定画面の「OK」ボタンは無効化され、クリックできなくなります。

プラグインの配置

プラグインは、"<user profile>\AppData\Roaming\Kasasagi\Sekirei\"以下のフォルダに配置することで、Sekireiに自動的に読み込まれます。<user profile>はユーザープロファイルの格納先です。例えば、Windowsにサインインしているユーザーの名前が"Test1"であった場合、"C:\Users\Test1"が<user profile>となります。実際にプラグインが機能する様子を確認するために、サンプルのプラグインを以下からダウンロードして配置してみます。

ダウンロード

ダウンロード

Kasasagi.Sekirei.Ram.zip

これは、ローカルメモリ上に構築されたディレクトリツリーにアクセスするためのプラグインです。設定は必要無いため、IProtocolインターフェースのUiプロパティの実装はnullを返します。

  1. "<user profile>\AppData\Roaming\Kasasagi\Sekirei\"にフォルダ"Ram"を作成。

    "<user profile>\AppData\Roaming\Kasasagi\Sekirei\"の直下にプラグインを配置した場合、(のち)にもう一つプラグインを配置しようとして同名のファイルを上書きしてしまうなど、混乱のもとと成りかねません。このフォルダに子フォルダを作成し、その中にプラグインを配置することをお勧めします。

  2. ダウンロードしたZIPファイルを解凍し、展開されたファイルを"Ram"フォルダ内に配置。

    必要なファイルを漏れなく配置してください。ただし、"Kasasagi.DavServer.dll"と"Kasasagi.Sekirei.Ui.dll"は必要ありません。これらはSekireiの一部として既にインストールされています。

  3. Sekireiを起動。

    Sekireiのメイン画面に「RAM」と書かれたボタンが新たに配置されています。

  4. 「RAM」ボタンをクリック。

    実装のUiプロパティがnullであるため、設定画面が表示されずにいきなり接続情報が生成されます。

  5. 接続情報のUNCをクリック。

    エクスプローラーが起動します。このUNC以下にファイルやフォルダを作成しても、これらは永続化されません。このプラグインの接続先はローカルメモリであるためです。コンピューターをシャットダウンすればこれらのファイルやフォルダは揮発します。

プラグインの除去

"<user profile>\AppData\Roaming\Kasasagi\Sekirei\"以下に配置されたプラグインのファイルを削除しようとすると、他のプロセスがアクセスしていて削除できない旨の警告が発せられる場合があります。この「他のプロセス」とは、Sekireiの実体である"Kasasagi.Sekirei.Clients.exe"に他なりません。プラグインを除去するには、先ず、この"Kasasagi.Sekirei.Clients.exe"を停止する必要があります。

  1. 「スタート」ボタンを右クリックして「タスクマネージャー(T)」を選択。

    タスクマネージャーが起動します。

  2. 「詳細」タブを選択。

  3. "Kasasagi.Sekirei.Clients.exe"を選択して「タスクの終了(E)」ボタンをクリック。

    確認のダイアログボックスが表示されます。

  4. 表示されたダイアログボックスの「プロセスの終了」ボタンをクリック。

    プロセスが強制終了されます。

  5. "<user profile>\AppData\Roaming\Kasasagi\Sekirei\"以下に配置されたプラグインのファイルを削除。

    正常にファイルが削除されるはずです。