LoginSignup
1
1

More than 3 years have passed since last update.

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

Posted at

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());
        }
    });
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1