Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9364

Windows版QuickLookのプラグインの読み込み・インストールのロジックを読む

$
0
0

OSXにある便利機能のQuickLook。それのwindows版のフリーソフトQuickLookのプラグイン部分のロジックを読んでみた。
調査したレポジトリのコミットはこちらgithub.com/QL-Win/QuickLook。2019年11月12日 22:32 JST時点のコミット。

まとめ

  • Namespaceプロパティが重要で、バージョンは関係なかった。UIに表示するだけで、値の大小比較はしていない
  • プラグインのインストール先はユーザーがインストールするもの、システムにプリインストールされているものの2つある。
  • インストール先は2つに別れているが、読み込み後に同じ配列に追加されてPriorityプロパティだけでソートしている。システムにプリインストールされているプラグインが優先される事も無いし、Priorityが同じ時にどうなるかは未定義。

QuickLookとは

OSXはファイルを選択してスペースキーを押すとプレビューが表示される機能がある。プレビューが表示されてもフォーカスは元のファイラー側にあり、表示中にカーソルキーで選択状態のファイルを変更するとプレビューの内容も更新される。
これをwindowsで再現したソフトがQuickLook。OSXの機能と同じ名前でちょっと紛らわしい。OS標準の機能ではないのでサクサク感は少し劣る。

プラグインをインストールするロジック

image.png

QuickLook.Plugin.PluginInstallerプロジェクトがプラグインインストーラー。

最初に実行されるのはPlugin.csのinitメソッド。この中でApp.UserPluginPathの中の"*.to_be_deleted"ファイルを削除している。
QuickLookのプラグインはインストール時に同じ名前のプラグインがあると、そのプラグインのファイルを削除するのではなく、xxx.to_be_deleted というファイルにリネームする処理がある。

public void Init()
{
    CleanupOldPlugins(App.UserPluginPath);
}
private static void CleanupOldPlugins(string folder)
{
    if (!Directory.Exists(folder))
        return;
    Directory.GetFiles(folder, "*.to_be_deleted", SearchOption.AllDirectories).ForEach(file =>
    {
        try
        {
            File.Delete(file);
        }
        catch (Exception)
        {
            // ignored
        }
    });
}

次はPluginInfoPanel.xaml.csのReadInfoメソッド。_path変数にはプラグインファイルのパスが入っている。
プラグインファイルを解凍し、"QuickLook.Plugin.Metadata.config"という名前固定のxmlファイルを読み込み。xmlの名前空間、説明文、バージョン情報を読み込む。
名前空間は"QuickLook.Plugin."から始まらない場合はエラーとなる。バージョン情報や説明文はUIに表示するだけ。

private void ReadInfo()
{
    filename.Text = Path.GetFileNameWithoutExtension(_path);

    var xml = LoadXml(GetFileFromZip(_path, "QuickLook.Plugin.Metadata.config"));

    _namespace = GetString(xml, @"/Metadata/Namespace");

    var okay = _namespace != null && _namespace.StartsWith("QuickLook.Plugin.");

    filename.Text = okay ? _namespace : "Invalid plugin.";
    version.Text = "Version " + GetString(xml, @"/Metadata/Version", "not defined");
    description.Text = GetString(xml, @"/Metadata/Description", string.Empty);

    btnInstall.Visibility = okay ? Visibility.Visible : Visibility.Collapsed;
}

プラグインをインストールボタンが押されたら同ファイルのDoInstallが実行。CleanUp()は既存のプラグインを.to_be_deletedファイルにリネームする。その後、インストールファイルを名前空間のディレクトリに解凍する。この後再起動を促すメッセージを表示してインストールは終了。

private Task DoInstall()
{
    var targetFolder = Path.Combine(App.UserPluginPath, _namespace);
    return new Task(() =>
    {
        CleanUp();

        try
        {
            ZipFile.ExtractToDirectory(_path, targetFolder);
        }
        catch (Exception ex)
        {
            Dispatcher.BeginInvoke(new Action(() => description.Text = ex.Message));
            Dispatcher.BeginInvoke(new Action(() => btnInstall.Content = "Installation failed."));
            CleanUp();
        }
    });

    void CleanUp()
    {
        if (!Directory.Exists(targetFolder))
        {
            Directory.CreateDirectory(targetFolder);
            return;
        }

        try
        {
            Directory.GetFiles(targetFolder, "*", SearchOption.AllDirectories)
                .ForEach(file => File.Move(file,
                    Path.Combine(Path.GetDirectoryName(file), Guid.NewGuid() + ".to_be_deleted")));
        }
        catch (Exception ex)
        {
            Dispatcher.BeginInvoke(new Action(() => description.Text = ex.Message));
            Dispatcher.BeginInvoke(new Action(() => btnInstall.Content = "Installation failed."));
        }
    }
}

プラグインをロードするロジック

PluginManager.csのコンストラクタでユーザが追加したプラグイン・システムデフォルトのプラグインを読み込んで初期化する。
App.UserPluginPathはC:\Users\UserName\AppData\Roaming\pooi.moe\QuickLook\QuickLook.Plugin\。App.AppPathはexeファイルの位置。

private PluginManager()
{
    LoadPlugins(App.UserPluginPath);
    LoadPlugins(Path.Combine(App.AppPath, "QuickLook.Plugin\\"));
    InitLoadedPlugins();
}

LoadPluginsの中身。フォルダに対して"QuickLook.Plugin.*.dll"のファイルを全てリストアップ。dllファイルを読み込んで、Priorityプロパティでソート。

private void LoadPlugins(string folder)
{
    if (!Directory.Exists(folder))
        return;

    Directory.GetFiles(folder, "QuickLook.Plugin.*.dll",
            SearchOption.AllDirectories)
        .ToList()
        .ForEach(
            lib =>
            {
                (from t in Assembly.LoadFrom(lib).GetExportedTypes()
                        where !t.IsInterface && !t.IsAbstract
                        where typeof(IViewer).IsAssignableFrom(t)
                        select t).ToList()
                    .ForEach(type => LoadedPlugins.Add(type.CreateInstance<IViewer>()));
            });

    LoadedPlugins = LoadedPlugins.OrderByDescending(i => i.Priority).ToList();
}

その後各プラグインのinitメソッドを呼ぶ。

private void InitLoadedPlugins()
{
    LoadedPlugins.ForEach(i =>
    {
        try
        {
            i.Init();
        }
        catch (Exception e)
        {
            ProcessHelper.WriteLog(e.ToString());
        }
    });
}

Viewing all articles
Browse latest Browse all 9364

Latest Images

Trending Articles