diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index 7355305201c..e20a476c51f 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -20,6 +20,8 @@ protected List LoadBookmarks(string browserDataPath, string name) var bookmarkPath = Path.Combine(profile, "Bookmarks"); if (!File.Exists(bookmarkPath)) continue; + + Main.RegisterBookmarkFile(bookmarkPath); var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source)); @@ -31,6 +33,7 @@ protected List LoadBookmarksFromFile(string path, string source) { if (!File.Exists(path)) return new(); + var bookmarks = new List(); using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 3df0347814c..892e18ddd31 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -12,7 +12,7 @@ public class FirefoxBookmarkLoader : IBookmarkLoader private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title FROM moz_places 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 "; @@ -29,21 +29,21 @@ public List GetBookmarks() return new List(); var bookmarkList = new List(); + + Main.RegisterBookmarkFile(PlacesPath); // create the connection string and init the connection string dbPath = string.Format(dbPathFormat, PlacesPath); - using (var dbConnection = new SQLiteConnection(dbPath)) - { - // Open connection to the database file and execute the query - dbConnection.Open(); - var reader = new SQLiteCommand(queryAllBookmarks, dbConnection).ExecuteReader(); - - // return results in List format - bookmarkList = reader.Select( - x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(), - x["url"].ToString()) - ).ToList(); - } + using var dbConnection = new SQLiteConnection(dbPath); + // Open connection to the database file and execute the query + dbConnection.Open(); + var reader = new SQLiteCommand(queryAllBookmarks, dbConnection).ExecuteReader(); + + // return results in List format + bookmarkList = reader.Select( + x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(), + x["url"].ToString()) + ).ToList(); return bookmarkList; } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index 1d58f84d89e..f9127cd3c2e 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -9,24 +9,29 @@ using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.BrowserBookmark.Views; using Flow.Launcher.Plugin.SharedCommands; +using System.IO; +using System.Threading.Channels; +using System.Threading.Tasks; 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 List cachedBookmarks = new List(); - private Settings _settings { get; set;} + private Settings _settings { get; set; } public void Init(PluginInitContext context) { this.context = context; - + _settings = context.API.LoadSettingJsonStorage(); cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings); + + _ = MonitorRefreshQueue(); } public List Query(Query query) @@ -52,7 +57,10 @@ public List Query(Query query) return true; }, - ContextData = new BookmarkAttributes { Url = c.Url } + ContextData = new BookmarkAttributes + { + Url = c.Url + } }).Where(r => r.Score > 0); return returnList.ToList(); } @@ -69,11 +77,64 @@ public List Query(Query query) context.API.OpenUrl(c.Url); return true; }, - ContextData = new BookmarkAttributes { Url = c.Url } + ContextData = new BookmarkAttributes + { + Url = c.Url + } }).ToList(); } } + + private static Channel refreshQueue = Channel.CreateBounded(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 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() { cachedBookmarks.Clear(); @@ -98,7 +159,8 @@ public Control CreateSettingPanel() public List LoadContextMenus(Result selectedResult) { - return new List() { + return new List() + { new Result { Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), @@ -114,7 +176,7 @@ public List LoadContextMenus(Result selectedResult) catch (Exception e) { var message = "Failed to set url in clipboard"; - Log.Exception("Main",message, e, "LoadContextMenus"); + Log.Exception("Main", message, e, "LoadContextMenus"); context.API.ShowMsg(message); @@ -122,12 +184,20 @@ public List LoadContextMenus(Result selectedResult) } }, IcoPath = "Images\\copylink.png" - }}; + } + }; } internal class BookmarkAttributes { internal string Url { get; set; } } + public void Dispose() + { + foreach (var watcher in Watchers) + { + watcher.Dispose(); + } + } } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json index afc4f985568..b93630c3181 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser Bookmarks", "Description": "Search your browser bookmarks", "Author": "qianlifeng, Ioannis G.", - "Version": "1.6.3", + "Version": "1.7.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index c4cd19eec0c..f0a53ed7758 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -18,13 +18,12 @@ 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 UWP.Application[] _uwps { 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; } @@ -51,29 +50,25 @@ public void Save() public async Task> QueryAsync(Query query, CancellationToken token) { - - if (IsStartupIndexProgramsRequired) - _ = IndexProgramsAsync(); - var result = await cache.GetOrCreateAsync(query.Search, async entry => - { - var resultList = await Task.Run(() => - _win32s.Cast() - .Concat(_uwps) - .AsParallel() - .WithCancellation(token) - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, Context.API)) - .Where(r => r?.Score > 0) - .ToList()); - - resultList = resultList.Any() ? resultList : emptyResults; - - entry.SetSize(resultList.Count); - entry.SetSlidingExpiration(TimeSpan.FromHours(8)); - - return resultList; - }); + { + var resultList = await Task.Run(() => + _win32s.Cast() + .Concat(_uwps) + .AsParallel() + .WithCancellation(token) + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, Context.API)) + .Where(r => r?.Score > 0) + .ToList()); + + resultList = resultList.Any() ? resultList : emptyResults; + + entry.SetSize(resultList.Count); + entry.SetSlidingExpiration(TimeSpan.FromHours(8)); + + return resultList; + }); return result; } @@ -84,62 +79,55 @@ public async Task InitAsync(PluginInitContext context) _settings = context.API.LoadSettingJsonStorage(); - await Task.Yield(); - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () => { _win32Storage = new BinaryStorage("Win32"); - _win32s = _win32Storage.TryLoad(new Win32[] { }); + _win32s = _win32Storage.TryLoad(new Win32[] + { + }); _uwpStorage = new BinaryStorage("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 uwps <{_uwps.Length}>"); - - bool indexedWinApps = false; - bool indexedUWPApps = false; + bool cacheEmpty = !_win32s.Any() && !_uwps.Any(); var a = Task.Run(() => { - if (IsStartupIndexProgramsRequired || !_win32s.Any()) - { - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs); - indexedWinApps = true; - } + Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs); }); var b = Task.Run(() => { - if (IsStartupIndexProgramsRequired || !_uwps.Any()) - { - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms); - indexedUWPApps = true; - } + Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms); }); - var indexTask = Task.WhenAll(a, b).ContinueWith(t => - { - if (indexedWinApps && indexedUWPApps) - _settings.LastIndexTime = DateTime.Today; - }, TaskScheduler.Current); + if (cacheEmpty) + await Task.WhenAll(a, b); - if (!(_win32s.Any() && _uwps.Any())) - await indexTask; + Win32.WatchProgramUpdate(_settings); + UWP.WatchPackageChange(); } public static void IndexWin32Programs() { var win32S = Win32.All(_settings); _win32s = win32S; + ResetCache(); } public static void IndexUwpPrograms() { var windows10 = new Version(10, 0); 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; + ResetCache(); } public static async Task IndexProgramsAsync() @@ -147,7 +135,6 @@ public static async Task IndexProgramsAsync() var t1 = Task.Run(IndexWin32Programs); var t2 = Task.Run(IndexUwpPrograms); await Task.WhenAll(t1, t2).ConfigureAwait(false); - ResetCache(); _settings.LastIndexTime = DateTime.Today; } @@ -209,13 +196,11 @@ private void DisableProgram(IProgram programToDelete) return; if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) - .FirstOrDefault() + _uwps.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) .Enabled = false; if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) - .FirstOrDefault() + _win32s.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) .Enabled = false; _settings.DisabledProgramSources @@ -248,5 +233,9 @@ public async Task ReloadDataAsync() { await IndexProgramsAsync(); } + public void Dispose() + { + Win32.Dispose(); + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 39a3589e328..ad7387f107f 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -18,6 +18,8 @@ using Rect = System.Windows.Rect; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Infrastructure.Logger; +using System.Runtime.Versioning; +using System.Threading.Channels; namespace Flow.Launcher.Plugin.Program.Programs { @@ -78,7 +80,7 @@ private void InitializeAppInfo() { var e = Marshal.GetExceptionForHR((int)hResult); 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().ToArray(); } @@ -89,30 +91,28 @@ private void InitializeAppInfo() } } - - /// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx private string[] XmlNamespaces(string path) { XDocument z = XDocument.Load(path); if (z.Root != null) { - var namespaces = z.Root.Attributes(). - Where(a => a.IsNamespaceDeclaration). - GroupBy( - a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName, - a => XNamespace.Get(a.Value) - ).Select( - g => g.First().ToString() - ).ToArray(); + var namespaces = z.Root.Attributes().Where(a => a.IsNamespaceDeclaration).GroupBy( + a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName, + a => XNamespace.Get(a.Value) + ).Select( + g => g.First().ToString() + ).ToArray(); return namespaces; } else { 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 @@ private void InitPackageVersion(string[] namespaces) { var versionFromNamespace = new Dictionary { - {"http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10}, - {"http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81}, - {"http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8}, + { + "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 + }, + { + "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 + }, + { + "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 + }, }; foreach (var n in versionFromNamespace.Keys) @@ -135,8 +141,8 @@ private void InitPackageVersion(string[] namespaces) } ProgramLogger.LogException($"|UWP|XmlNamespaces|{Location}" + - "|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version " - + $"{FullName} from location {Location} is returned.", new FormatException()); + "|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version " + + $"{FullName} from location {Location} is returned.", new FormatException()); Version = PackageVersion.Unknown; } @@ -171,15 +177,17 @@ public static Application[] All() }).ToArray(); var updatedListWithoutDisabledApps = applications - .Where(t1 => !Main._settings.DisabledProgramSources - .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) - .Select(x => x); + .Where(t1 => !Main._settings.DisabledProgramSources + .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) + .Select(x => x); return updatedListWithoutDisabledApps.ToArray(); } else { - return new Application[] { }; + return new Application[] + { + }; } } @@ -215,7 +223,7 @@ private static IEnumerable CurrentUserPackages() catch (Exception e) { 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; } @@ -225,7 +233,42 @@ private static IEnumerable CurrentUserPackages() } else { - return new Package[] { }; + return new Package[] + { + }; + } + } + + private static Channel PackageChangeChannel = Channel.CreateBounded(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 @@ public Result Result(string query, IPublicAPI api) e.SpecialKeyState.ShiftPressed && !e.SpecialKeyState.AltPressed && !e.SpecialKeyState.WinPressed - ); + ); if (elevated && CanRunElevated) { @@ -358,14 +401,12 @@ public List ContextMenus(IPublicAPI api) new Result { Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), - Action = _ => { Main.Context.API.OpenDirectory(Package.Location); return true; }, - IcoPath = "Images/folder.png" } }; @@ -414,8 +455,7 @@ private void LaunchElevated() var info = new ProcessStartInfo(command) { - UseShellExecute = true, - Verb = "runas", + UseShellExecute = true, Verb = "runas", }; Main.StartProcess(Process.Start, info); @@ -492,7 +532,7 @@ internal string ResourceFromPri(string packageFullName, string packageName, stri else { 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; } } @@ -532,9 +572,15 @@ internal string LogoUriFromManifest(AppxPackageHelper.IAppxManifestApplication a { var logoKeyFromVersion = new Dictionary { - { PackageVersion.Windows10, "Square44x44Logo" }, - { PackageVersion.Windows81, "Square30x30Logo" }, - { PackageVersion.Windows8, "SmallLogo" }, + { + PackageVersion.Windows10, "Square44x44Logo" + }, + { + PackageVersion.Windows81, "Square30x30Logo" + }, + { + PackageVersion.Windows8, "SmallLogo" + }, }; if (logoKeyFromVersion.ContainsKey(Package.Version)) { @@ -571,14 +617,40 @@ internal string LogoPathFromUri(string uri) { var end = path.Length - extension.Length; var prefix = path.Substring(0, end); - var paths = new List { path }; + var paths = new List + { + path + }; var scaleFactors = new Dictionary> { // 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 { 100, 125, 150, 200, 400 } }, - { PackageVersion.Windows81, new List { 100, 120, 140, 160, 180 } }, - { PackageVersion.Windows8, new List { 100 } } + { + PackageVersion.Windows10, new List + { + 100, + 125, + 150, + 200, + 400 + } + }, + { + PackageVersion.Windows81, new List + { + 100, + 120, + 140, + 160, + 180 + } + }, + { + PackageVersion.Windows8, new List + { + 100 + } + } }; if (scaleFactors.ContainsKey(Package.Version)) @@ -597,15 +669,15 @@ internal string LogoPathFromUri(string uri) else { 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; } } else { ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + - $"|Unable to find extension from {uri} for {UserModelId} " + - $"in package location {Package.Location}", new FileNotFoundException()); + $"|Unable to find extension from {uri} for {UserModelId} " + + $"in package location {Package.Location}", new FileNotFoundException()); return string.Empty; } } @@ -632,8 +704,8 @@ private BitmapImage ImageFromPath(string path) else { ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Avaliable" : path)}" + - $"|Unable to get logo for {UserModelId} from {path} and" + - $" located in {Package.Location}", new FileNotFoundException()); + $"|Unable to get logo for {UserModelId} from {path} and" + + $" located in {Package.Location}", new FileNotFoundException()); return new BitmapImage(new Uri(Constant.MissingImgIcon)); } } @@ -681,8 +753,8 @@ private ImageSource PlatedImage(BitmapImage image) else { ProgramLogger.LogException($"|UWP|PlatedImage|{Package.Location}" + - $"|Unable to convert background string {BackgroundColor} " + - $"to color for {Package.Location}", new InvalidOperationException()); + $"|Unable to convert background string {BackgroundColor} " + + $"to color for {Package.Location}", new InvalidOperationException()); return new BitmapImage(new Uri(Constant.MissingImgIcon)); } @@ -727,5 +799,6 @@ private static extern Hresult SHCreateStreamOnFileEx(string fileName, Stgm grfMo [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] private static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved); + } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index ba6ada3b687..6a8b232e94e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -12,9 +12,11 @@ using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Infrastructure.Logger; +using System.Collections; using System.Diagnostics; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; using System.Diagnostics.CodeAnalysis; +using System.Threading.Channels; namespace Flow.Launcher.Plugin.Program.Programs { @@ -109,7 +111,7 @@ public Result Result(string query, IPublicAPI api) c.SpecialKeyState.ShiftPressed && !c.SpecialKeyState.AltPressed && !c.SpecialKeyState.WinPressed - ); + ); var info = new ProcessStartInfo { @@ -194,6 +196,9 @@ public override string ToString() return Name; } + public static List Watchers = new List(); + + private static Win32 Win32Program(string path) { try @@ -216,7 +221,10 @@ private static Win32 Win32Program(string path) ProgramLogger.LogException($"|Win32|Win32Program|{path}" + $"|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 @@ private static Win32 ExeProgram(string path) ProgramLogger.LogException($"|Win32|ExeProgram|{path}" + $"|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 @@ private static IEnumerable ProgramPaths(string directory, string[] suffi return Directory.EnumerateFiles(directory, "*", new EnumerationOptions { - IgnoreInaccessible = true, - RecurseSubdirectories = true + IgnoreInaccessible = true, RecurseSubdirectories = true }).Where(x => suffixes.Contains(Extension(x))); } @@ -545,5 +555,70 @@ public bool Equals([AllowNull] Win32 other) return UniqueIdentifier == other.UniqueIdentifier; } + + private static IEnumerable 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(); + 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 indexQueue = Channel.CreateBounded(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(); + } + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index 993b2adba19..a297cb410c4 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "1.8.2", + "Version": "1.9.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",