mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-10-20 02:29:45 +00:00
227 lines
7.7 KiB
C#
227 lines
7.7 KiB
C#
// Copyright © 2017-2025 QL-Win Contributors
|
|
//
|
|
// This file is part of QuickLook program.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
using QuickLook.Common.ExtensionMethods;
|
|
using QuickLook.Common.Helpers;
|
|
using QuickLook.Common.Plugin;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Windows;
|
|
using UnblockZoneIdentifier;
|
|
|
|
namespace QuickLook;
|
|
|
|
internal class PluginManager
|
|
{
|
|
private static PluginManager _instance;
|
|
|
|
private PluginManager()
|
|
{
|
|
LoadPlugins(App.UserPluginPath);
|
|
LoadPlugins(Path.Combine(App.AppPath, "QuickLook.Plugin\\"));
|
|
InitLoadedPlugins();
|
|
}
|
|
|
|
internal IViewer DefaultPlugin { get; } = new Plugin.InfoPanel.Plugin();
|
|
|
|
internal List<IViewer> LoadedPlugins { get; private set; } = [];
|
|
|
|
internal static PluginManager GetInstance()
|
|
{
|
|
return _instance ??= new PluginManager();
|
|
}
|
|
|
|
internal IViewer FindMatch(string path)
|
|
{
|
|
if (string.IsNullOrEmpty(path))
|
|
return null;
|
|
|
|
var matched = GetInstance()
|
|
.LoadedPlugins.FirstOrDefault(plugin =>
|
|
{
|
|
var can = false;
|
|
try
|
|
{
|
|
var timer = new Stopwatch();
|
|
timer.Start();
|
|
|
|
can = plugin.CanHandle(path);
|
|
|
|
timer.Stop();
|
|
Debug.WriteLine($"{plugin.GetType()}: {can}, {timer.ElapsedMilliseconds}ms");
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// ignored
|
|
}
|
|
|
|
return can;
|
|
});
|
|
|
|
return (matched ?? DefaultPlugin).GetType().CreateInstance<IViewer>();
|
|
}
|
|
|
|
private void LoadPlugins(string folder)
|
|
{
|
|
if (!Directory.Exists(folder))
|
|
return;
|
|
|
|
var failedPlugins = new List<(string Plugin, Exception Error)>();
|
|
|
|
Directory.GetFiles(folder, "QuickLook.Plugin.*.dll", SearchOption.AllDirectories)
|
|
.ToList()
|
|
.ForEach(lib =>
|
|
{
|
|
try
|
|
{
|
|
(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>()));
|
|
}
|
|
// 0x80131515: ERROR_ASSEMBLY_FILE_BLOCKED - Windows blocked the assembly due to security policy
|
|
catch (FileLoadException ex) when (ex.HResult == unchecked((int)0x80131515) && SettingHelper.IsPortableVersion())
|
|
{
|
|
if (!HandleSecurityBlockedException()) throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log the error
|
|
ProcessHelper.WriteLog($"Failed to load plugin {Path.GetFileName(lib)}: {ex}");
|
|
failedPlugins.Add((Path.GetFileName(lib), ex));
|
|
}
|
|
});
|
|
|
|
LoadedPlugins = [.. LoadedPlugins.OrderByDescending(i => i.Priority)];
|
|
|
|
// If any plugins failed to load, show a message box with the details
|
|
if (failedPlugins.Any())
|
|
{
|
|
var message = "The following plugins failed to load:\n\n" +
|
|
string.Join("\n", failedPlugins.Select(f => $"• {f.Plugin}"));
|
|
|
|
MessageBox.Show(
|
|
message,
|
|
"Some Plugins Failed to Load",
|
|
MessageBoxButton.OK,
|
|
MessageBoxImage.Warning);
|
|
}
|
|
}
|
|
|
|
private void InitLoadedPlugins()
|
|
{
|
|
LoadedPlugins.ForEach(i =>
|
|
{
|
|
try
|
|
{
|
|
i.Init();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
ProcessHelper.WriteLog(e.ToString());
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the case when Windows has blocked plugin files due to security policy.
|
|
/// Attempts automatic unblock first, then shows manual instructions if that fails.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <para>true if automatic unblock succeeded and app is restarting.</para>
|
|
/// <para>false if manual intervention is needed and exception should be thrown.</para>
|
|
/// </returns>
|
|
private static bool HandleSecurityBlockedException()
|
|
{
|
|
var triedUnblock = SettingHelper.Get("TriedUnblock", false);
|
|
if (!triedUnblock)
|
|
{
|
|
SettingHelper.Set("TriedUnblock", true);
|
|
if (TryUnblockFilesAndRestart()) return true;
|
|
}
|
|
|
|
// Show manual unblock instructions if automatic unblock failed or was already attempted
|
|
MessageBox.Show(
|
|
"""
|
|
Windows has blocked the plugins.
|
|
To fix this, please follow these steps:
|
|
1. Right-click the downloaded QuickLook zip file and select 'Properties'
|
|
2. At the bottom of the Properties window, check 'Unblock'
|
|
3. Click 'Apply' and 'OK'
|
|
4. Extract the zip file again
|
|
QuickLook will now close. Please launch it from the unblocked folder.
|
|
""",
|
|
"Security Block Detected",
|
|
MessageBoxButton.OK,
|
|
MessageBoxImage.Error);
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to automatically unblock all files in the application directory using PowerShell's Unblock-File cmdlet.
|
|
/// If successful, restarts the application to apply the changes.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <para>true if the unblock command succeeded and application restart was initiated.</para>
|
|
/// <para>false if the unblock command failed, in which case manual unblock instructions should be shown.</para>
|
|
/// </returns>
|
|
private static bool TryUnblockFilesAndRestart()
|
|
{
|
|
ProcessHelper.WriteLog("Attempting automatic unblock of plugins...");
|
|
|
|
try
|
|
{
|
|
var rootDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
|
|
|
if (!string.IsNullOrEmpty(rootDir) && Directory.Exists(rootDir))
|
|
{
|
|
foreach (var filePath in Directory.GetFiles(rootDir, "*.*", SearchOption.AllDirectories))
|
|
{
|
|
if (ZoneIdentifierManager.IsZoneBlocked(filePath))
|
|
{
|
|
_ = ZoneIdentifierManager.UnblockZone(filePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
MessageBox.Show(
|
|
"""
|
|
QuickLook has detected that Windows blocked the plugins, and has attempted to unblock them.
|
|
The application will now restart to check if the unblocking was successful.
|
|
""",
|
|
"Security Unblock Attempt",
|
|
MessageBoxButton.OK,
|
|
MessageBoxImage.Information);
|
|
|
|
// Restart the application using TrayIconManager
|
|
TrayIconManager.GetInstance().Restart(forced: true);
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
ProcessHelper.WriteLog($"Failed to perform automatic unblock: {e}");
|
|
return false;
|
|
}
|
|
}
|
|
}
|