Merge pull request #967 from Flow-Launcher/filewatcher

Auto re-index for Program and Bookmark plugins
This commit is contained in:
Jeremy Wu 2022-08-30 07:14:02 +10:00 committed by GitHub
commit c74eafb9d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 333 additions and 123 deletions

View file

@ -21,6 +21,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
if (!File.Exists(bookmarkPath)) if (!File.Exists(bookmarkPath))
continue; continue;
Main.RegisterBookmarkFile(bookmarkPath);
var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})");
bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source)); bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source));
} }
@ -31,6 +33,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
{ {
if (!File.Exists(path)) if (!File.Exists(path))
return new(); return new();
var bookmarks = new List<Bookmark>(); var bookmarks = new List<Bookmark>();
using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path));
if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement))

View file

@ -12,7 +12,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title
FROM moz_places FROM moz_places
INNER JOIN moz_bookmarks ON ( INNER JOIN moz_bookmarks ON (
moz_bookmarks.fk NOT NULL AND moz_bookmarks.fk = moz_places.id moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id
) )
ORDER BY moz_places.visit_count DESC ORDER BY moz_places.visit_count DESC
"; ";
@ -30,20 +30,20 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
var bookmarkList = new List<Bookmark>(); var bookmarkList = new List<Bookmark>();
Main.RegisterBookmarkFile(PlacesPath);
// create the connection string and init the connection // create the connection string and init the connection
string dbPath = string.Format(dbPathFormat, PlacesPath); string dbPath = string.Format(dbPathFormat, PlacesPath);
using (var dbConnection = new SQLiteConnection(dbPath)) using var dbConnection = new SQLiteConnection(dbPath);
{ // Open connection to the database file and execute the query
// Open connection to the database file and execute the query dbConnection.Open();
dbConnection.Open(); var reader = new SQLiteCommand(queryAllBookmarks, dbConnection).ExecuteReader();
var reader = new SQLiteCommand(queryAllBookmarks, dbConnection).ExecuteReader();
// return results in List<Bookmark> format // return results in List<Bookmark> format
bookmarkList = reader.Select( bookmarkList = reader.Select(
x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(), x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(),
x["url"].ToString()) x["url"].ToString())
).ToList(); ).ToList();
}
return bookmarkList; return bookmarkList;
} }

View file

@ -9,16 +9,19 @@ using Flow.Launcher.Plugin.BrowserBookmark.Commands;
using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.BrowserBookmark.Models;
using Flow.Launcher.Plugin.BrowserBookmark.Views; using Flow.Launcher.Plugin.BrowserBookmark.Views;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using System.IO;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin.BrowserBookmark namespace Flow.Launcher.Plugin.BrowserBookmark
{ {
public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable
{ {
private PluginInitContext context; private PluginInitContext context;
private List<Bookmark> cachedBookmarks = new List<Bookmark>(); private List<Bookmark> cachedBookmarks = new List<Bookmark>();
private Settings _settings { get; set;} private Settings _settings { get; set; }
public void Init(PluginInitContext context) public void Init(PluginInitContext context)
{ {
@ -27,6 +30,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
_settings = context.API.LoadSettingJsonStorage<Settings>(); _settings = context.API.LoadSettingJsonStorage<Settings>();
cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings); cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings);
_ = MonitorRefreshQueue();
} }
public List<Result> Query(Query query) public List<Result> Query(Query query)
@ -52,7 +57,10 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
return true; return true;
}, },
ContextData = new BookmarkAttributes { Url = c.Url } ContextData = new BookmarkAttributes
{
Url = c.Url
}
}).Where(r => r.Score > 0); }).Where(r => r.Score > 0);
return returnList.ToList(); return returnList.ToList();
} }
@ -69,11 +77,64 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
context.API.OpenUrl(c.Url); context.API.OpenUrl(c.Url);
return true; return true;
}, },
ContextData = new BookmarkAttributes { Url = c.Url } ContextData = new BookmarkAttributes
{
Url = c.Url
}
}).ToList(); }).ToList();
} }
} }
private static Channel<byte> refreshQueue = Channel.CreateBounded<byte>(1);
private async Task MonitorRefreshQueue()
{
var reader = refreshQueue.Reader;
while (await reader.WaitToReadAsync())
{
await Task.Delay(2000);
if (reader.TryRead(out _))
{
ReloadData();
}
}
}
private static readonly List<FileSystemWatcher> Watchers = new();
internal static void RegisterBookmarkFile(string path)
{
var directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory))
return;
var watcher = new FileSystemWatcher(directory!);
if (File.Exists(path))
{
var fileName = Path.GetFileName(path);
watcher.Filter = fileName;
}
watcher.NotifyFilter = NotifyFilters.FileName |
NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.Size;
watcher.Changed += static (_, _) =>
{
refreshQueue.Writer.TryWrite(default);
};
watcher.Renamed += static (_, _) =>
{
refreshQueue.Writer.TryWrite(default);
};
watcher.EnableRaisingEvents = true;
Watchers.Add(watcher);
}
public void ReloadData() public void ReloadData()
{ {
cachedBookmarks.Clear(); cachedBookmarks.Clear();
@ -98,7 +159,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
public List<Result> LoadContextMenus(Result selectedResult) public List<Result> LoadContextMenus(Result selectedResult)
{ {
return new List<Result>() { return new List<Result>()
{
new Result new Result
{ {
Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"),
@ -114,7 +176,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
catch (Exception e) catch (Exception e)
{ {
var message = "Failed to set url in clipboard"; var message = "Failed to set url in clipboard";
Log.Exception("Main",message, e, "LoadContextMenus"); Log.Exception("Main", message, e, "LoadContextMenus");
context.API.ShowMsg(message); context.API.ShowMsg(message);
@ -122,12 +184,20 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
} }
}, },
IcoPath = "Images\\copylink.png" IcoPath = "Images\\copylink.png"
}}; }
};
} }
internal class BookmarkAttributes internal class BookmarkAttributes
{ {
internal string Url { get; set; } internal string Url { get; set; }
} }
public void Dispose()
{
foreach (var watcher in Watchers)
{
watcher.Dispose();
}
}
} }
} }

View file

@ -4,7 +4,7 @@
"Name": "Browser Bookmarks", "Name": "Browser Bookmarks",
"Description": "Search your browser bookmarks", "Description": "Search your browser bookmarks",
"Author": "qianlifeng, Ioannis G.", "Author": "qianlifeng, Ioannis G.",
"Version": "1.6.3", "Version": "1.7.0",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll",

View file

@ -18,13 +18,12 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher.Plugin.Program namespace Flow.Launcher.Plugin.Program
{ {
public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable, IDisposable
{ {
internal static Win32[] _win32s { get; set; } internal static Win32[] _win32s { get; set; }
internal static UWP.Application[] _uwps { get; set; } internal static UWP.Application[] _uwps { get; set; }
internal static Settings _settings { get; set; } internal static Settings _settings { get; set; }
private static bool IsStartupIndexProgramsRequired => _settings.LastIndexTime.AddDays(3) < DateTime.Today;
internal static PluginInitContext Context { get; private set; } internal static PluginInitContext Context { get; private set; }
@ -51,29 +50,25 @@ namespace Flow.Launcher.Plugin.Program
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token) public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{ {
if (IsStartupIndexProgramsRequired)
_ = IndexProgramsAsync();
var result = await cache.GetOrCreateAsync(query.Search, async entry => var result = await cache.GetOrCreateAsync(query.Search, async entry =>
{ {
var resultList = await Task.Run(() => var resultList = await Task.Run(() =>
_win32s.Cast<IProgram>() _win32s.Cast<IProgram>()
.Concat(_uwps) .Concat(_uwps)
.AsParallel() .AsParallel()
.WithCancellation(token) .WithCancellation(token)
.Where(p => p.Enabled) .Where(p => p.Enabled)
.Select(p => p.Result(query.Search, Context.API)) .Select(p => p.Result(query.Search, Context.API))
.Where(r => r?.Score > 0) .Where(r => r?.Score > 0)
.ToList()); .ToList());
resultList = resultList.Any() ? resultList : emptyResults; resultList = resultList.Any() ? resultList : emptyResults;
entry.SetSize(resultList.Count); entry.SetSize(resultList.Count);
entry.SetSlidingExpiration(TimeSpan.FromHours(8)); entry.SetSlidingExpiration(TimeSpan.FromHours(8));
return resultList; return resultList;
}); });
return result; return result;
} }
@ -84,62 +79,55 @@ namespace Flow.Launcher.Plugin.Program
_settings = context.API.LoadSettingJsonStorage<Settings>(); _settings = context.API.LoadSettingJsonStorage<Settings>();
await Task.Yield();
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () => Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () =>
{ {
_win32Storage = new BinaryStorage<Win32[]>("Win32"); _win32Storage = new BinaryStorage<Win32[]>("Win32");
_win32s = _win32Storage.TryLoad(new Win32[] { }); _win32s = _win32Storage.TryLoad(new Win32[]
{
});
_uwpStorage = new BinaryStorage<UWP.Application[]>("UWP"); _uwpStorage = new BinaryStorage<UWP.Application[]>("UWP");
_uwps = _uwpStorage.TryLoad(new UWP.Application[] { }); _uwps = _uwpStorage.TryLoad(new UWP.Application[]
{
});
}); });
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>"); Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
bool cacheEmpty = !_win32s.Any() && !_uwps.Any();
bool indexedWinApps = false;
bool indexedUWPApps = false;
var a = Task.Run(() => var a = Task.Run(() =>
{ {
if (IsStartupIndexProgramsRequired || !_win32s.Any()) Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
{
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
indexedWinApps = true;
}
}); });
var b = Task.Run(() => var b = Task.Run(() =>
{ {
if (IsStartupIndexProgramsRequired || !_uwps.Any()) Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms);
{
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms);
indexedUWPApps = true;
}
}); });
var indexTask = Task.WhenAll(a, b).ContinueWith(t => if (cacheEmpty)
{ await Task.WhenAll(a, b);
if (indexedWinApps && indexedUWPApps)
_settings.LastIndexTime = DateTime.Today;
}, TaskScheduler.Current);
if (!(_win32s.Any() && _uwps.Any())) Win32.WatchProgramUpdate(_settings);
await indexTask; UWP.WatchPackageChange();
} }
public static void IndexWin32Programs() public static void IndexWin32Programs()
{ {
var win32S = Win32.All(_settings); var win32S = Win32.All(_settings);
_win32s = win32S; _win32s = win32S;
ResetCache();
} }
public static void IndexUwpPrograms() public static void IndexUwpPrograms()
{ {
var windows10 = new Version(10, 0); var windows10 = new Version(10, 0);
var support = Environment.OSVersion.Version.Major >= windows10.Major; var support = Environment.OSVersion.Version.Major >= windows10.Major;
var applications = support ? UWP.All() : new UWP.Application[] { }; var applications = support ? UWP.All() : new UWP.Application[]
{
};
_uwps = applications; _uwps = applications;
ResetCache();
} }
public static async Task IndexProgramsAsync() public static async Task IndexProgramsAsync()
@ -147,7 +135,6 @@ namespace Flow.Launcher.Plugin.Program
var t1 = Task.Run(IndexWin32Programs); var t1 = Task.Run(IndexWin32Programs);
var t2 = Task.Run(IndexUwpPrograms); var t2 = Task.Run(IndexUwpPrograms);
await Task.WhenAll(t1, t2).ConfigureAwait(false); await Task.WhenAll(t1, t2).ConfigureAwait(false);
ResetCache();
_settings.LastIndexTime = DateTime.Today; _settings.LastIndexTime = DateTime.Today;
} }
@ -209,13 +196,11 @@ namespace Flow.Launcher.Plugin.Program
return; return;
if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
_uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) _uwps.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
.FirstOrDefault()
.Enabled = false; .Enabled = false;
if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
_win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) _win32s.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
.FirstOrDefault()
.Enabled = false; .Enabled = false;
_settings.DisabledProgramSources _settings.DisabledProgramSources
@ -248,5 +233,9 @@ namespace Flow.Launcher.Plugin.Program
{ {
await IndexProgramsAsync(); await IndexProgramsAsync();
} }
public void Dispose()
{
Win32.Dispose();
}
} }
} }

View file

@ -18,6 +18,8 @@ using Flow.Launcher.Plugin.Program.Logger;
using Rect = System.Windows.Rect; using Rect = System.Windows.Rect;
using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using System.Runtime.Versioning;
using System.Threading.Channels;
namespace Flow.Launcher.Plugin.Program.Programs namespace Flow.Launcher.Plugin.Program.Programs
{ {
@ -78,7 +80,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{ {
var e = Marshal.GetExceptionForHR((int)hResult); var e = Marshal.GetExceptionForHR((int)hResult);
ProgramLogger.LogException($"|UWP|InitializeAppInfo|{path}" + ProgramLogger.LogException($"|UWP|InitializeAppInfo|{path}" +
"|Error caused while trying to get the details of the UWP program", e); "|Error caused while trying to get the details of the UWP program", e);
Apps = new List<Application>().ToArray(); Apps = new List<Application>().ToArray();
} }
@ -89,30 +91,28 @@ namespace Flow.Launcher.Plugin.Program.Programs
} }
} }
/// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx /// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
private string[] XmlNamespaces(string path) private string[] XmlNamespaces(string path)
{ {
XDocument z = XDocument.Load(path); XDocument z = XDocument.Load(path);
if (z.Root != null) if (z.Root != null)
{ {
var namespaces = z.Root.Attributes(). var namespaces = z.Root.Attributes().Where(a => a.IsNamespaceDeclaration).GroupBy(
Where(a => a.IsNamespaceDeclaration). a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName,
GroupBy( a => XNamespace.Get(a.Value)
a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName, ).Select(
a => XNamespace.Get(a.Value) g => g.First().ToString()
).Select( ).ToArray();
g => g.First().ToString()
).ToArray();
return namespaces; return namespaces;
} }
else else
{ {
ProgramLogger.LogException($"|UWP|XmlNamespaces|{path}" + ProgramLogger.LogException($"|UWP|XmlNamespaces|{path}" +
$"|Error occured while trying to get the XML from {path}", new ArgumentNullException()); $"|Error occured while trying to get the XML from {path}", new ArgumentNullException());
return new string[] { }; return new string[]
{
};
} }
} }
@ -120,9 +120,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
{ {
var versionFromNamespace = new Dictionary<string, PackageVersion> var versionFromNamespace = new Dictionary<string, PackageVersion>
{ {
{"http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10}, {
{"http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81}, "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10
{"http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8}, },
{
"http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81
},
{
"http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8
},
}; };
foreach (var n in versionFromNamespace.Keys) foreach (var n in versionFromNamespace.Keys)
@ -135,8 +141,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
} }
ProgramLogger.LogException($"|UWP|XmlNamespaces|{Location}" + ProgramLogger.LogException($"|UWP|XmlNamespaces|{Location}" +
"|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version " "|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version "
+ $"{FullName} from location {Location} is returned.", new FormatException()); + $"{FullName} from location {Location} is returned.", new FormatException());
Version = PackageVersion.Unknown; Version = PackageVersion.Unknown;
} }
@ -171,15 +177,17 @@ namespace Flow.Launcher.Plugin.Program.Programs
}).ToArray(); }).ToArray();
var updatedListWithoutDisabledApps = applications var updatedListWithoutDisabledApps = applications
.Where(t1 => !Main._settings.DisabledProgramSources .Where(t1 => !Main._settings.DisabledProgramSources
.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => x); .Select(x => x);
return updatedListWithoutDisabledApps.ToArray(); return updatedListWithoutDisabledApps.ToArray();
} }
else else
{ {
return new Application[] { }; return new Application[]
{
};
} }
} }
@ -215,7 +223,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
catch (Exception e) catch (Exception e)
{ {
ProgramLogger.LogException("UWP", "CurrentUserPackages", $"id", "An unexpected error occured and " ProgramLogger.LogException("UWP", "CurrentUserPackages", $"id", "An unexpected error occured and "
+ $"unable to verify if package is valid", e); + $"unable to verify if package is valid", e);
return false; return false;
} }
@ -225,7 +233,42 @@ namespace Flow.Launcher.Plugin.Program.Programs
} }
else else
{ {
return new Package[] { }; return new Package[]
{
};
}
}
private static Channel<byte> PackageChangeChannel = Channel.CreateBounded<byte>(1);
public static async Task WatchPackageChange()
{
if (Environment.OSVersion.Version.Major >= 10)
{
var catalog = PackageCatalog.OpenForCurrentUser();
catalog.PackageInstalling += (_, args) =>
{
if (args.IsComplete)
PackageChangeChannel.Writer.TryWrite(default);
};
catalog.PackageUninstalling += (_, args) =>
{
if (args.IsComplete)
PackageChangeChannel.Writer.TryWrite(default);
};
catalog.PackageUpdating += (_, args) =>
{
if (args.IsComplete)
PackageChangeChannel.Writer.TryWrite(default);
};
while (await PackageChangeChannel.Reader.WaitToReadAsync().ConfigureAwait(false))
{
await Task.Delay(3000).ConfigureAwait(false);
PackageChangeChannel.Reader.TryRead(out _);
await Task.Run(Main.IndexUwpPrograms);
}
} }
} }
@ -325,7 +368,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
e.SpecialKeyState.ShiftPressed && e.SpecialKeyState.ShiftPressed &&
!e.SpecialKeyState.AltPressed && !e.SpecialKeyState.AltPressed &&
!e.SpecialKeyState.WinPressed !e.SpecialKeyState.WinPressed
); );
if (elevated && CanRunElevated) if (elevated && CanRunElevated)
{ {
@ -358,14 +401,12 @@ namespace Flow.Launcher.Plugin.Program.Programs
new Result new Result
{ {
Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"),
Action = _ => Action = _ =>
{ {
Main.Context.API.OpenDirectory(Package.Location); Main.Context.API.OpenDirectory(Package.Location);
return true; return true;
}, },
IcoPath = "Images/folder.png" IcoPath = "Images/folder.png"
} }
}; };
@ -414,8 +455,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
var info = new ProcessStartInfo(command) var info = new ProcessStartInfo(command)
{ {
UseShellExecute = true, UseShellExecute = true, Verb = "runas",
Verb = "runas",
}; };
Main.StartProcess(Process.Start, info); Main.StartProcess(Process.Start, info);
@ -492,7 +532,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
else else
{ {
ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result " ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result "
+ $"pri {source} in uwp location {Package.Location}", new NullReferenceException()); + $"pri {source} in uwp location {Package.Location}", new NullReferenceException());
return string.Empty; return string.Empty;
} }
} }
@ -532,9 +572,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
{ {
var logoKeyFromVersion = new Dictionary<PackageVersion, string> var logoKeyFromVersion = new Dictionary<PackageVersion, string>
{ {
{ PackageVersion.Windows10, "Square44x44Logo" }, {
{ PackageVersion.Windows81, "Square30x30Logo" }, PackageVersion.Windows10, "Square44x44Logo"
{ PackageVersion.Windows8, "SmallLogo" }, },
{
PackageVersion.Windows81, "Square30x30Logo"
},
{
PackageVersion.Windows8, "SmallLogo"
},
}; };
if (logoKeyFromVersion.ContainsKey(Package.Version)) if (logoKeyFromVersion.ContainsKey(Package.Version))
{ {
@ -571,14 +617,40 @@ namespace Flow.Launcher.Plugin.Program.Programs
{ {
var end = path.Length - extension.Length; var end = path.Length - extension.Length;
var prefix = path.Substring(0, end); var prefix = path.Substring(0, end);
var paths = new List<string> { path }; var paths = new List<string>
{
path
};
var scaleFactors = new Dictionary<PackageVersion, List<int>> var scaleFactors = new Dictionary<PackageVersion, List<int>>
{ {
// scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables, // scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables,
{ PackageVersion.Windows10, new List<int> { 100, 125, 150, 200, 400 } }, {
{ PackageVersion.Windows81, new List<int> { 100, 120, 140, 160, 180 } }, PackageVersion.Windows10, new List<int>
{ PackageVersion.Windows8, new List<int> { 100 } } {
100,
125,
150,
200,
400
}
},
{
PackageVersion.Windows81, new List<int>
{
100,
120,
140,
160,
180
}
},
{
PackageVersion.Windows8, new List<int>
{
100
}
}
}; };
if (scaleFactors.ContainsKey(Package.Version)) if (scaleFactors.ContainsKey(Package.Version))
@ -597,15 +669,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
else else
{ {
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
$"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException()); $"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException());
return string.Empty; return string.Empty;
} }
} }
else else
{ {
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
$"|Unable to find extension from {uri} for {UserModelId} " + $"|Unable to find extension from {uri} for {UserModelId} " +
$"in package location {Package.Location}", new FileNotFoundException()); $"in package location {Package.Location}", new FileNotFoundException());
return string.Empty; return string.Empty;
} }
} }
@ -632,8 +704,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
else else
{ {
ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Avaliable" : path)}" + ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Avaliable" : path)}" +
$"|Unable to get logo for {UserModelId} from {path} and" + $"|Unable to get logo for {UserModelId} from {path} and" +
$" located in {Package.Location}", new FileNotFoundException()); $" located in {Package.Location}", new FileNotFoundException());
return new BitmapImage(new Uri(Constant.MissingImgIcon)); return new BitmapImage(new Uri(Constant.MissingImgIcon));
} }
} }
@ -681,8 +753,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
else else
{ {
ProgramLogger.LogException($"|UWP|PlatedImage|{Package.Location}" + ProgramLogger.LogException($"|UWP|PlatedImage|{Package.Location}" +
$"|Unable to convert background string {BackgroundColor} " + $"|Unable to convert background string {BackgroundColor} " +
$"to color for {Package.Location}", new InvalidOperationException()); $"to color for {Package.Location}", new InvalidOperationException());
return new BitmapImage(new Uri(Constant.MissingImgIcon)); return new BitmapImage(new Uri(Constant.MissingImgIcon));
} }
@ -727,5 +799,6 @@ namespace Flow.Launcher.Plugin.Program.Programs
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, private static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf,
IntPtr ppvReserved); IntPtr ppvReserved);
} }
} }

View file

@ -12,9 +12,11 @@ using Flow.Launcher.Plugin.Program.Logger;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using System.Collections;
using System.Diagnostics; using System.Diagnostics;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Threading.Channels;
namespace Flow.Launcher.Plugin.Program.Programs namespace Flow.Launcher.Plugin.Program.Programs
{ {
@ -109,7 +111,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
c.SpecialKeyState.ShiftPressed && c.SpecialKeyState.ShiftPressed &&
!c.SpecialKeyState.AltPressed && !c.SpecialKeyState.AltPressed &&
!c.SpecialKeyState.WinPressed !c.SpecialKeyState.WinPressed
); );
var info = new ProcessStartInfo var info = new ProcessStartInfo
{ {
@ -194,6 +196,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
return Name; return Name;
} }
public static List<FileSystemWatcher> Watchers = new List<FileSystemWatcher>();
private static Win32 Win32Program(string path) private static Win32 Win32Program(string path)
{ {
try try
@ -216,7 +221,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
ProgramLogger.LogException($"|Win32|Win32Program|{path}" + ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
$"|Permission denied when trying to load the program from {path}", e); $"|Permission denied when trying to load the program from {path}", e);
return new Win32() { Valid = false, Enabled = false }; return new Win32()
{
Valid = false, Enabled = false
};
} }
} }
@ -294,7 +302,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" + ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
$"|Permission denied when trying to load the program from {path}", e); $"|Permission denied when trying to load the program from {path}", e);
return new Win32() { Valid = false, Enabled = false }; return new Win32()
{
Valid = false, Enabled = false
};
} }
} }
@ -305,8 +316,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
return Directory.EnumerateFiles(directory, "*", new EnumerationOptions return Directory.EnumerateFiles(directory, "*", new EnumerationOptions
{ {
IgnoreInaccessible = true, IgnoreInaccessible = true, RecurseSubdirectories = true
RecurseSubdirectories = true
}).Where(x => suffixes.Contains(Extension(x))); }).Where(x => suffixes.Contains(Extension(x)));
} }
@ -545,5 +555,70 @@ namespace Flow.Launcher.Plugin.Program.Programs
return UniqueIdentifier == other.UniqueIdentifier; return UniqueIdentifier == other.UniqueIdentifier;
} }
private static IEnumerable<string> GetStartMenuPaths()
{
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms);
return new[]
{
directory1, directory2
};
}
public static void WatchProgramUpdate(Settings settings)
{
var paths = new List<string>();
if (settings.EnableStartMenuSource)
paths.AddRange(GetStartMenuPaths());
paths.AddRange(from source in settings.ProgramSources where source.Enabled select source.Location);
foreach (var directory in from path in paths where Directory.Exists(path) select path)
{
WatchDirectory(directory);
}
_ = Task.Run(MonitorDirectoryChangeAsync);
}
private static Channel<byte> indexQueue = Channel.CreateBounded<byte>(1);
public static async Task MonitorDirectoryChangeAsync()
{
var reader = indexQueue.Reader;
while (await reader.WaitToReadAsync())
{
await Task.Delay(500);
while (reader.TryRead(out _))
{
}
await Task.Run(Main.IndexWin32Programs);
}
}
public static void WatchDirectory(string directory)
{
if (!Directory.Exists(directory))
{
throw new ArgumentException("Path Not Exist");
}
var watcher = new FileSystemWatcher(directory);
watcher.Created += static (_, _) => indexQueue.Writer.TryWrite(default);
watcher.Deleted += static (_, _) => indexQueue.Writer.TryWrite(default);
watcher.EnableRaisingEvents = true;
watcher.IncludeSubdirectories = true;
Watchers.Add(watcher);
}
public static void Dispose()
{
foreach (var fileSystemWatcher in Watchers)
{
fileSystemWatcher.Dispose();
}
}
} }
} }

View file

@ -4,7 +4,7 @@
"Name": "Program", "Name": "Program",
"Description": "Search programs in Flow.Launcher", "Description": "Search programs in Flow.Launcher",
"Author": "qianlifeng", "Author": "qianlifeng",
"Version": "1.8.2", "Version": "1.9.0",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",