From 9f3a652aecb80d7792a0733594e805749445c9c7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 12 Jan 2018 00:40:04 +0100 Subject: [PATCH 1/8] Move focus to main window of process if it's already running --- src/Jarvis.Addin.Files/FileProvider.cs | 32 ++++++++++++++++++++++++- src/Jarvis.Core/Interop/Kernel32.cs | 15 ++++++++++++ src/Jarvis.Core/Interop/Win32.Window.cs | 6 +++++ src/Jarvis.Core/Jarvis.Core.csproj | 1 + 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/Jarvis.Core/Interop/Kernel32.cs diff --git a/src/Jarvis.Addin.Files/FileProvider.cs b/src/Jarvis.Addin.Files/FileProvider.cs index 71b26f4..5677364 100644 --- a/src/Jarvis.Addin.Files/FileProvider.cs +++ b/src/Jarvis.Addin.Files/FileProvider.cs @@ -7,14 +7,18 @@ using System.Diagnostics; using System.Linq; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Media; using Jarvis.Addin.Files.Drawing; using Jarvis.Addin.Files.Indexing; using Jarvis.Core; +using Jarvis.Core.Interop; using JetBrains.Annotations; +using Spectre.System.IO; using static Jarvis.Addin.Files.Sources.Uwp.ShellInterop; +using Path = System.IO.Path; namespace Jarvis.Addin.Files { @@ -55,7 +59,33 @@ protected override Task ExecuteAsync(FileResult result) if (result.Path.Scheme == "shell") { - Process.Start(path); + var descriptionPath = result.Description.Replace("/", "\\"); + var existingProcess = Process + .GetProcesses() + .FirstOrDefault(process => + { + var processPath = new StringBuilder(1024); + var size = processPath.Capacity; + var processHandle = Kernel32.OpenProcess(0x1000, false, process.Id); + Kernel32.QueryFullProcessImageName(processHandle, 0, processPath, out size); + return processPath.ToString() == descriptionPath; + }); + + if (existingProcess != null) + { + Win32.SetForegroundWindow(existingProcess.MainWindowHandle); + + var rect = new Win32.W32Rect(); + Win32.GetWindowRect(existingProcess.MainWindowHandle, ref rect); + if (rect.Top < 0 && rect.Right < 0 && rect.Bottom < 0 && rect.Left < 0) // Window is minimized + { + Win32.ShowWindow(existingProcess.MainWindowHandle, 1); + } + } + else + { + Process.Start(path); + } } else if (result.Path.Scheme == "uwp") { diff --git a/src/Jarvis.Core/Interop/Kernel32.cs b/src/Jarvis.Core/Interop/Kernel32.cs new file mode 100644 index 0000000..5c70ee4 --- /dev/null +++ b/src/Jarvis.Core/Interop/Kernel32.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Jarvis.Core.Interop +{ + public static class Kernel32 + { + [DllImport("kernel32.dll")] + public static extern IntPtr OpenProcess(int access, bool inherit, int proccessId); + + [DllImport("kernel32.dll")] + public static extern IntPtr QueryFullProcessImageName(IntPtr hprocess, int dwFlags, [Out] StringBuilder lpExeName, [Out] [MarshalAs(UnmanagedType.U4)] out int size); + } +} \ No newline at end of file diff --git a/src/Jarvis.Core/Interop/Win32.Window.cs b/src/Jarvis.Core/Interop/Win32.Window.cs index 5859747..44757fe 100644 --- a/src/Jarvis.Core/Interop/Win32.Window.cs +++ b/src/Jarvis.Core/Interop/Win32.Window.cs @@ -14,5 +14,11 @@ public static partial class Win32 [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hwnd, ref W32Rect rectangle); + + [DllImport("user32.dll")] + public static extern bool SetForegroundWindow(IntPtr hwnd); + + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hwnd, int nCmdShow); } } diff --git a/src/Jarvis.Core/Jarvis.Core.csproj b/src/Jarvis.Core/Jarvis.Core.csproj index 549bdfc..e4e6fa1 100644 --- a/src/Jarvis.Core/Jarvis.Core.csproj +++ b/src/Jarvis.Core/Jarvis.Core.csproj @@ -79,6 +79,7 @@ + From ee0c8b6a3607b4ca359c852294a9e7564ce94ed7 Mon Sep 17 00:00:00 2001 From: Jan Karger Date: Fri, 12 Jan 2018 22:30:30 +0100 Subject: [PATCH 2/8] Fix ClearType for TextBox and TextBlock This issue is caused by `AllowsTransparency="True"` More info: https://blog.magnusmontin.net/2016/07/07/enabling-cleartype-on-a-textbox-in-a-transparent-wpf-window/ --- src/Jarvis/Jarvis.csproj | 4 +++ src/Jarvis/JarvisApplication.xaml | 1 + src/Jarvis/Resources/JarvisStyles.xaml | 45 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/Jarvis/Resources/JarvisStyles.xaml diff --git a/src/Jarvis/Jarvis.csproj b/src/Jarvis/Jarvis.csproj index ef5507f..7119820 100644 --- a/src/Jarvis/Jarvis.csproj +++ b/src/Jarvis/Jarvis.csproj @@ -187,6 +187,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/Jarvis/JarvisApplication.xaml b/src/Jarvis/JarvisApplication.xaml index 512f4fa..0b4efbf 100644 --- a/src/Jarvis/JarvisApplication.xaml +++ b/src/Jarvis/JarvisApplication.xaml @@ -8,6 +8,7 @@ + diff --git a/src/Jarvis/Resources/JarvisStyles.xaml b/src/Jarvis/Resources/JarvisStyles.xaml new file mode 100644 index 0000000..95ed3df --- /dev/null +++ b/src/Jarvis/Resources/JarvisStyles.xaml @@ -0,0 +1,45 @@ + + + + + + + \ No newline at end of file From cdfd80e048acc521922687024cddadd297314a1e Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Thu, 11 Jan 2018 01:07:41 +0100 Subject: [PATCH 3/8] Add file indexing settings. * Set folders to index and exclude from indexing. * Allow indexing of specific extensions only. * Ignore hidden files and system files. Closes #43 --- src/Jarvis.Addin.Files/Constants.cs | 26 ++++++ src/Jarvis.Addin.Files/FileAddin.cs | 4 +- src/Jarvis.Addin.Files/FileProvider.cs | 5 +- src/Jarvis.Addin.Files/FileSettings.cs | 82 +++++++++++++++++++ src/Jarvis.Addin.Files/FileSettingsSeeder.cs | 44 ++++++++++ .../{Drawing => Icons}/IconLoader.cs | 2 +- .../{Drawing => Icons}/ShellIconLoader.cs | 2 +- .../{Drawing => Icons}/UwpIconLoader.cs | 2 +- .../Indexing/FileIndexer.cs | 46 +++++++---- .../Indexing/TriggerIndexMessage.cs | 10 +++ .../Jarvis.Addin.Files.csproj | 53 +++++++++++- .../Sources/DocumentIndexSource.cs | 71 +++++++++++++--- .../ViewModels/ExcludeFolderViewModel.cs | 29 +++++++ .../ViewModels/ExcludePatternViewModel.cs | 44 ++++++++++ .../ViewModels/FileSettingsViewModel.cs | 78 ++++++++++++++++++ .../ViewModels/IncludeExtensionsViewModel.cs | 44 ++++++++++ .../ViewModels/IncludeFolderViewModel.cs | 29 +++++++ .../ViewModels/SelectFolderViewModel.cs | 53 ++++++++++++ .../ViewModels/SelectPatternViewModel.cs | 69 ++++++++++++++++ .../Views/ExcludeFolderView.xaml | 27 ++++++ .../Views/ExcludePatternView.xaml | 24 ++++++ .../Views/FileSettingsView.xaml | 39 +++++++++ .../Views/IncludeExtensionsView.xaml | 24 ++++++ .../Views/IncludeFolderView.xaml | 27 ++++++ src/Jarvis.Addin.Files/packages.config | 1 + .../Extensions/CollectionExtensions.cs | 23 ++++++ src/Jarvis.Core/ISettings.cs | 3 + src/Jarvis.Core/ISettingsSeeder.cs | 11 +++ src/Jarvis.Core/ISettingsStore.cs | 1 + src/Jarvis.Core/Jarvis.Core.csproj | 2 + src/Jarvis/Bootstrapping/JarvisModule.cs | 32 ++++++-- .../Seeding/GeneralSettingsSeeder.cs | 26 ++++++ src/Jarvis/Jarvis.csproj | 1 + src/Jarvis/Services/SettingsService.cs | 70 ++++++++++------ .../Settings/GeneralSettingsViewModel.cs | 4 + src/Jarvis/ViewModels/SettingsViewModel.cs | 4 + src/Jarvis/Views/SettingsView.xaml | 43 +++++----- 37 files changed, 963 insertions(+), 92 deletions(-) create mode 100644 src/Jarvis.Addin.Files/Constants.cs create mode 100644 src/Jarvis.Addin.Files/FileSettings.cs create mode 100644 src/Jarvis.Addin.Files/FileSettingsSeeder.cs rename src/Jarvis.Addin.Files/{Drawing => Icons}/IconLoader.cs (98%) rename src/Jarvis.Addin.Files/{Drawing => Icons}/ShellIconLoader.cs (98%) rename src/Jarvis.Addin.Files/{Drawing => Icons}/UwpIconLoader.cs (98%) create mode 100644 src/Jarvis.Addin.Files/Indexing/TriggerIndexMessage.cs create mode 100644 src/Jarvis.Addin.Files/ViewModels/ExcludeFolderViewModel.cs create mode 100644 src/Jarvis.Addin.Files/ViewModels/ExcludePatternViewModel.cs create mode 100644 src/Jarvis.Addin.Files/ViewModels/FileSettingsViewModel.cs create mode 100644 src/Jarvis.Addin.Files/ViewModels/IncludeExtensionsViewModel.cs create mode 100644 src/Jarvis.Addin.Files/ViewModels/IncludeFolderViewModel.cs create mode 100644 src/Jarvis.Addin.Files/ViewModels/SelectFolderViewModel.cs create mode 100644 src/Jarvis.Addin.Files/ViewModels/SelectPatternViewModel.cs create mode 100644 src/Jarvis.Addin.Files/Views/ExcludeFolderView.xaml create mode 100644 src/Jarvis.Addin.Files/Views/ExcludePatternView.xaml create mode 100644 src/Jarvis.Addin.Files/Views/FileSettingsView.xaml create mode 100644 src/Jarvis.Addin.Files/Views/IncludeExtensionsView.xaml create mode 100644 src/Jarvis.Addin.Files/Views/IncludeFolderView.xaml create mode 100644 src/Jarvis.Core/Extensions/CollectionExtensions.cs create mode 100644 src/Jarvis.Core/ISettingsSeeder.cs create mode 100644 src/Jarvis/Bootstrapping/Seeding/GeneralSettingsSeeder.cs diff --git a/src/Jarvis.Addin.Files/Constants.cs b/src/Jarvis.Addin.Files/Constants.cs new file mode 100644 index 0000000..1a8bfb9 --- /dev/null +++ b/src/Jarvis.Addin.Files/Constants.cs @@ -0,0 +1,26 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Jarvis.Addin.Files +{ + internal sealed class Constants + { + internal sealed class Settings + { + public const string Version = "Files.Version"; + + internal sealed class Include + { + public const string Folders = "Files.Include.Folders"; + public const string Extensions = "Files.Include.Extensions"; + } + + internal sealed class Exclude + { + public const string Folders = "Files.Exclude.Folders"; + public const string Patterns = "Files.Exclude.Patterns"; + } + } + } +} diff --git a/src/Jarvis.Addin.Files/FileAddin.cs b/src/Jarvis.Addin.Files/FileAddin.cs index c6db8db..2a21a1d 100644 --- a/src/Jarvis.Addin.Files/FileAddin.cs +++ b/src/Jarvis.Addin.Files/FileAddin.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using Autofac; -using Jarvis.Addin.Files.Drawing; +using Jarvis.Addin.Files.Icons; using Jarvis.Addin.Files.Indexing; using Jarvis.Addin.Files.Sources; using Jarvis.Addin.Files.Sources.Uwp; @@ -18,12 +18,14 @@ public void Configure(ContainerBuilder builder) { builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); // Sources builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + // Utilities builder.RegisterType().SingleInstance(); builder.RegisterType().SingleInstance(); builder.RegisterType().As().SingleInstance(); diff --git a/src/Jarvis.Addin.Files/FileProvider.cs b/src/Jarvis.Addin.Files/FileProvider.cs index 5677364..c57e0d2 100644 --- a/src/Jarvis.Addin.Files/FileProvider.cs +++ b/src/Jarvis.Addin.Files/FileProvider.cs @@ -11,8 +11,9 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Media; -using Jarvis.Addin.Files.Drawing; +using Jarvis.Addin.Files.Icons; using Jarvis.Addin.Files.Indexing; +using Jarvis.Addin.Files.ViewModels; using Jarvis.Core; using Jarvis.Core.Interop; using JetBrains.Annotations; @@ -23,7 +24,7 @@ namespace Jarvis.Addin.Files { [UsedImplicitly] - internal sealed class FileProvider : QueryProvider + internal sealed class FileProvider : QueryProvider { private readonly IFileIndex _index; private readonly IconLoader _loader; diff --git a/src/Jarvis.Addin.Files/FileSettings.cs b/src/Jarvis.Addin.Files/FileSettings.cs new file mode 100644 index 0000000..ccd9c23 --- /dev/null +++ b/src/Jarvis.Addin.Files/FileSettings.cs @@ -0,0 +1,82 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Jarvis.Core; +using Spectre.System.IO; + +namespace Jarvis.Addin.Files +{ + internal sealed class FileSettings + { + public int Version { get; set; } + public HashSet IncludedFolders { get; } + public HashSet IncludedExtensions { get; } + public HashSet ExcludedFolders { get; } + public HashSet ExcludedPatterns { get; } + + public const int CurrentVersion = 1; + + public FileSettings() + { + IncludedFolders = new HashSet(new PathComparer(false)); + IncludedExtensions = new HashSet(StringComparer.Ordinal); + ExcludedFolders = new HashSet(new PathComparer(false)); + ExcludedPatterns = new HashSet(StringComparer.Ordinal); + } + + public static FileSettings Load(ISettingsStore settings) + { + var model = new FileSettings + { + Version = settings.Get(Constants.Settings.Version) + }; + + // Load included folders. + var includeFolders = settings.Get(Constants.Settings.Include.Folders); + if (includeFolders != null) + { + model.IncludedFolders.AddRange(includeFolders + .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => new DirectoryPath(s))); + } + + // Load included extensions. + var includeExtensions = settings.Get(Constants.Settings.Include.Extensions); + if (!string.IsNullOrWhiteSpace(includeExtensions)) + { + model.IncludedExtensions.AddRange(includeExtensions.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)); + } + + // Load excluded folders. + var excludeFolders = settings.Get(Constants.Settings.Exclude.Folders); + if (excludeFolders != null) + { + model.ExcludedFolders.AddRange(excludeFolders + .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => new DirectoryPath(s))); + } + + // Load excluded patterns. + var excludePatterns = settings.Get(Constants.Settings.Exclude.Patterns); + if (!string.IsNullOrWhiteSpace(excludePatterns)) + { + model.ExcludedPatterns.AddRange(excludePatterns.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)); + } + + return model; + } + + public void Save(ISettingsStore settings) + { + settings.Set(Constants.Settings.Version, CurrentVersion); + settings.Set(Constants.Settings.Include.Folders, string.Join("|", IncludedFolders.Select(p => p.FullPath))); + settings.Set(Constants.Settings.Include.Extensions, string.Join("|", IncludedExtensions)); + settings.Set(Constants.Settings.Exclude.Folders, string.Join("|", ExcludedFolders.Select(p => p.FullPath))); + settings.Set(Constants.Settings.Exclude.Patterns, string.Join("|", ExcludedPatterns)); + } + } +} diff --git a/src/Jarvis.Addin.Files/FileSettingsSeeder.cs b/src/Jarvis.Addin.Files/FileSettingsSeeder.cs new file mode 100644 index 0000000..bff7b8e --- /dev/null +++ b/src/Jarvis.Addin.Files/FileSettingsSeeder.cs @@ -0,0 +1,44 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Jarvis.Core; +using Spectre.System.IO; + +namespace Jarvis.Addin.Files +{ + internal sealed class FileSettingsSeeder : ISettingsSeeder + { + public void Seed(ISettingsStore store) + { + var model = FileSettings.Load(store); + + if (model.Version == 0) + { + Initialize(model); + model.Save(store); + } + } + + private static void Initialize(FileSettings model) + { + // Included folders. + model.IncludedFolders.Add(new DirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments))); + model.IncludedFolders.Add(new DirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.MyMusic))); + model.IncludedFolders.Add(new DirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.MyVideos))); + model.IncludedFolders.Add(new DirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures))); + + // Included extensions. + model.IncludedExtensions.AddRange(new[] + { + "ai", "avi", "doc", "docx", "eps", "flv", "gif", "htm", "html", + "jpeg", "jpg", "mov", "mp3", "mp4", "mpg", "mpeg", "odt", "ogg", "ogv", "pdf", "png", "ppt", "psd", + "rar", "rtf", "svg", "txt", "wav", "wma", "xls", "xlsx", "zip" + }); + + // Excluded patterns. + model.ExcludedPatterns.AddRange(new[] { ".git", "node_modules", "packages" }); + } + } +} diff --git a/src/Jarvis.Addin.Files/Drawing/IconLoader.cs b/src/Jarvis.Addin.Files/Icons/IconLoader.cs similarity index 98% rename from src/Jarvis.Addin.Files/Drawing/IconLoader.cs rename to src/Jarvis.Addin.Files/Icons/IconLoader.cs index 6085a8e..70c79ab 100644 --- a/src/Jarvis.Addin.Files/Drawing/IconLoader.cs +++ b/src/Jarvis.Addin.Files/Icons/IconLoader.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using System.Windows.Media; -namespace Jarvis.Addin.Files.Drawing +namespace Jarvis.Addin.Files.Icons { internal sealed class IconLoader { diff --git a/src/Jarvis.Addin.Files/Drawing/ShellIconLoader.cs b/src/Jarvis.Addin.Files/Icons/ShellIconLoader.cs similarity index 98% rename from src/Jarvis.Addin.Files/Drawing/ShellIconLoader.cs rename to src/Jarvis.Addin.Files/Icons/ShellIconLoader.cs index 7ad45cc..b62b093 100644 --- a/src/Jarvis.Addin.Files/Drawing/ShellIconLoader.cs +++ b/src/Jarvis.Addin.Files/Icons/ShellIconLoader.cs @@ -13,7 +13,7 @@ using System.Windows.Media.Imaging; using Jarvis.Core.Interop; -namespace Jarvis.Addin.Files.Drawing +namespace Jarvis.Addin.Files.Icons { internal static class ShellIconLoader { diff --git a/src/Jarvis.Addin.Files/Drawing/UwpIconLoader.cs b/src/Jarvis.Addin.Files/Icons/UwpIconLoader.cs similarity index 98% rename from src/Jarvis.Addin.Files/Drawing/UwpIconLoader.cs rename to src/Jarvis.Addin.Files/Icons/UwpIconLoader.cs index bf09cc6..57b24b5 100644 --- a/src/Jarvis.Addin.Files/Drawing/UwpIconLoader.cs +++ b/src/Jarvis.Addin.Files/Icons/UwpIconLoader.cs @@ -10,7 +10,7 @@ using ColorConverter = System.Windows.Media.ColorConverter; using Pen = System.Windows.Media.Pen; -namespace Jarvis.Addin.Files.Drawing +namespace Jarvis.Addin.Files.Icons { internal static class UwpIconLoader { diff --git a/src/Jarvis.Addin.Files/Indexing/FileIndexer.cs b/src/Jarvis.Addin.Files/Indexing/FileIndexer.cs index cc14242..530f665 100644 --- a/src/Jarvis.Addin.Files/Indexing/FileIndexer.cs +++ b/src/Jarvis.Addin.Files/Indexing/FileIndexer.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Caliburn.Micro; using Jarvis.Addin.Files.Collections; using Jarvis.Core; using Jarvis.Core.Diagnostics; @@ -19,25 +20,29 @@ namespace Jarvis.Addin.Files.Indexing { [UsedImplicitly] - internal sealed class FileIndexer : IBackgroundWorker, IFileIndex + internal sealed class FileIndexer : IBackgroundWorker, IFileIndex, IHandle { private readonly IJarvisLog _log; private readonly List _sources; private readonly HashSet _stopWords; private readonly ScoreComparer _comparer; private readonly IndexedEntryComparer _entryComparer; + private readonly ManualResetEvent _trigger; private Trie _trie; public string Name => "File indexing service"; - public FileIndexer(IEnumerable sources, IJarvisLog log) + public FileIndexer(IEventAggregator events, IEnumerable sources, IJarvisLog log) { _log = new LogDecorator("FileIndexer", log); _sources = new List(sources ?? Array.Empty()); _stopWords = new HashSet(StringComparer.OrdinalIgnoreCase) { "to", "the" }; _comparer = new ScoreComparer(); _entryComparer = new IndexedEntryComparer(); + _trigger = new ManualResetEvent(false); + + events.Subscribe(this); } public Task Run(CancellationToken token) @@ -46,8 +51,8 @@ public Task Run(CancellationToken token) { while (true) { - var st = new Stopwatch(); - st.Start(); + var indexingWatch = new Stopwatch(); + indexingWatch.Start(); var result = LoadResults(token); @@ -57,8 +62,8 @@ public Task Run(CancellationToken token) } _log.Debug("Updating index..."); - var st2 = new Stopwatch(); - st2.Start(); + var indexUpdateWatch = new Stopwatch(); + indexUpdateWatch.Start(); var trie = new Trie(); foreach (var file in result) @@ -86,8 +91,8 @@ public Task Run(CancellationToken token) } } - st2.Stop(); - _log.Debug($"Building trie took {st2.ElapsedMilliseconds}ms"); + indexUpdateWatch.Stop(); + _log.Debug($"Building trie took {indexUpdateWatch.ElapsedMilliseconds}ms"); _log.Debug("Writing index..."); Interlocked.Exchange(ref _trie, trie); @@ -95,22 +100,30 @@ public Task Run(CancellationToken token) _log.Verbose($"Nodes: {_trie.NodeCount}"); _log.Verbose($"Items: {_trie.ItemCount}"); - // Wait for a minute. - st.Stop(); - _log.Debug($"Indexing done. Took {st.ElapsedMilliseconds}ms"); + indexingWatch.Stop(); + _log.Debug($"Indexing done. Took {indexingWatch.ElapsedMilliseconds}ms"); - if (token.WaitHandle.WaitOne((int)TimeSpan.FromMinutes(5).TotalMilliseconds)) + // Wait for a while. + var index = WaitHandle.WaitAny(new[] { token.WaitHandle, _trigger }, (int)TimeSpan.FromMinutes(5).TotalMilliseconds); + if (index == 0) { _log.Information("We were instructed to stop (2)."); break; } + + // Triggered update? + if (index == 1) + { + _log.Information("A re-index was triggered."); + _trigger.Reset(); + } } return true; - }); + }, token); } - private List LoadResults(CancellationToken token) + private IEnumerable LoadResults(CancellationToken token) { // Ask all sources for files. return _sources.AsParallel() @@ -183,5 +196,10 @@ private static float CalculateScore(IndexedEntry entry, string query) return Math.Min(LevenshteinScorer.Score(entry.Title, query), LevenshteinScorer.Score(entry.Description ?? entry.Title, query)); } + + public void Handle(TriggerIndexMessage message) + { + _trigger.Set(); + } } } \ No newline at end of file diff --git a/src/Jarvis.Addin.Files/Indexing/TriggerIndexMessage.cs b/src/Jarvis.Addin.Files/Indexing/TriggerIndexMessage.cs new file mode 100644 index 0000000..cd80941 --- /dev/null +++ b/src/Jarvis.Addin.Files/Indexing/TriggerIndexMessage.cs @@ -0,0 +1,10 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Jarvis.Addin.Files.Indexing +{ + internal sealed class TriggerIndexMessage + { + } +} diff --git a/src/Jarvis.Addin.Files/Jarvis.Addin.Files.csproj b/src/Jarvis.Addin.Files/Jarvis.Addin.Files.csproj index 4909346..9cf7b1a 100644 --- a/src/Jarvis.Addin.Files/Jarvis.Addin.Files.csproj +++ b/src/Jarvis.Addin.Files/Jarvis.Addin.Files.csproj @@ -35,6 +35,15 @@ ..\packages\Autofac.4.6.2\lib\net45\Autofac.dll + + ..\packages\Caliburn.Micro.Core.3.2.0\lib\net45\Caliburn.Micro.dll + + + ..\packages\Caliburn.Micro.3.2.0\lib\net45\Caliburn.Micro.Platform.dll + + + ..\packages\Caliburn.Micro.3.2.0\lib\net45\Caliburn.Micro.Platform.Core.dll + ..\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll @@ -62,6 +71,10 @@ + + + C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7\System.Xaml.dll + @@ -84,13 +97,16 @@ - + + + + @@ -98,6 +114,7 @@ + @@ -112,11 +129,18 @@ - + - + + + + + + + + @@ -147,7 +171,28 @@ - + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + diff --git a/src/Jarvis.Addin.Files/Sources/DocumentIndexSource.cs b/src/Jarvis.Addin.Files/Sources/DocumentIndexSource.cs index 3f53adf..2906a48 100644 --- a/src/Jarvis.Addin.Files/Sources/DocumentIndexSource.cs +++ b/src/Jarvis.Addin.Files/Sources/DocumentIndexSource.cs @@ -4,9 +4,13 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; using Jarvis.Addin.Files.Extensions; using Jarvis.Addin.Files.Indexing; using Jarvis.Core; +using Jarvis.Core.Diagnostics; using JetBrains.Annotations; using Spectre.System.IO; @@ -16,34 +20,38 @@ namespace Jarvis.Addin.Files.Sources internal sealed class DocumentIndexSource : IFileIndexSource { private readonly IFileSystem _fileSystem; + private readonly ISettingsStore _settings; + private readonly IJarvisLog _log; public string Name => "User document"; - public DocumentIndexSource(IFileSystem fileSystem) + public DocumentIndexSource(IFileSystem fileSystem, ISettingsStore settings, IJarvisLog log) { _fileSystem = fileSystem; + _settings = settings; + _log = log; } public IEnumerable Index() { - var folders = new[] - { - new DirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)), - new DirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)), - new DirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.MyVideos)), - new DirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)) - }; + var settings = FileSettings.Load(_settings); + var extensions = new HashSet(settings.IncludedExtensions, StringComparer.OrdinalIgnoreCase); + var regexes = settings.ExcludedPatterns.Select(s => new Regex(s, RegexOptions.Singleline)).ToList(); - foreach (var folder in folders) + foreach (var folder in settings.IncludedFolders) { - foreach (var entry in Index(folder)) + foreach (var entry in Index(folder, settings.ExcludedFolders, extensions, regexes)) { yield return entry; } } } - private IEnumerable Index(DirectoryPath path) + private IEnumerable Index( + DirectoryPath path, + HashSet excludedFolders, + HashSet includedExtensions, + List excludedPatterns) { var stack = new Stack(); stack.Push(path); @@ -52,6 +60,20 @@ private IEnumerable Index(DirectoryPath path) { var current = stack.Pop(); + // Folder excluded? + if (excludedFolders.Contains(current)) + { + _log.Debug($"Folder '{current.FullPath}' has been excluded."); + continue; + } + + // Folder name not allowed? + if (TryMatchPattern(current, excludedPatterns, out var pattern)) + { + _log.Debug($"Folder '{current.FullPath}' matched pattern '{pattern}' that has been excluded."); + continue; + } + var directory = _fileSystem.GetDirectorySafe(current); if (directory != null) { @@ -78,6 +100,18 @@ private IEnumerable Index(DirectoryPath path) // TODO: Temporary fix due to Spectre.System lib. continue; } + if ((file.Attributes & FileAttributes.System) == FileAttributes.System) + { + continue; + } + if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + continue; + } + if (!includedExtensions.Contains(file.Path.GetExtension().Name)) + { + continue; + } yield return DocumentIndexSourceEntry.File(file.Path, file.Path.GetFilename().FullPath, file.Path.FullPath, @@ -90,5 +124,20 @@ private IEnumerable Index(DirectoryPath path) } } } + + private static bool TryMatchPattern(DirectoryPath current, IEnumerable excludedPatterns, out Regex pattern) + { + pattern = null; + var currentName = current.GetDirectoryName(); + foreach (var excludedPattern in excludedPatterns) + { + if (excludedPattern.IsMatch(currentName)) + { + pattern = excludedPattern; + return true; + } + } + return false; + } } } diff --git a/src/Jarvis.Addin.Files/ViewModels/ExcludeFolderViewModel.cs b/src/Jarvis.Addin.Files/ViewModels/ExcludeFolderViewModel.cs new file mode 100644 index 0000000..d7ecb4a --- /dev/null +++ b/src/Jarvis.Addin.Files/ViewModels/ExcludeFolderViewModel.cs @@ -0,0 +1,29 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Jarvis.Core; + +namespace Jarvis.Addin.Files.ViewModels +{ + internal sealed class ExcludeFolderViewModel : SelectFolderViewModel + { + protected override string Description => "Select a folder to exclude from indexing."; + + public override ValidationResult Validate() + { + return ValidationResult.Ok(); + } + + public override void Load(FileSettings settings) + { + Items.AddRange(settings.ExcludedFolders); + } + + public override void Populate(ref FileSettings settings) + { + settings.ExcludedFolders.Clear(); + settings.ExcludedFolders.AddRange(Items); + } + } +} diff --git a/src/Jarvis.Addin.Files/ViewModels/ExcludePatternViewModel.cs b/src/Jarvis.Addin.Files/ViewModels/ExcludePatternViewModel.cs new file mode 100644 index 0000000..0afe2f9 --- /dev/null +++ b/src/Jarvis.Addin.Files/ViewModels/ExcludePatternViewModel.cs @@ -0,0 +1,44 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Linq; +using Jarvis.Core; + +namespace Jarvis.Addin.Files.ViewModels +{ + internal sealed class ExcludePatternViewModel : SelectPatternViewModel + { + public override ValidationResult Validate() + { + return ValidationResult.Ok(); + } + + public override string Process(string value) + { + return value?.Trim(); + } + + public override bool IsValid(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + var invalidCharacters = Path.GetInvalidPathChars(); + return value.All(character => !invalidCharacters.Contains(character)); + } + + public override void Load(FileSettings settings) + { + Items.AddRange(settings.ExcludedPatterns); + } + + public override void Populate(ref FileSettings settings) + { + settings.ExcludedPatterns.Clear(); + settings.ExcludedPatterns.AddRange(Items); + } + } +} diff --git a/src/Jarvis.Addin.Files/ViewModels/FileSettingsViewModel.cs b/src/Jarvis.Addin.Files/ViewModels/FileSettingsViewModel.cs new file mode 100644 index 0000000..8c6fbf6 --- /dev/null +++ b/src/Jarvis.Addin.Files/ViewModels/FileSettingsViewModel.cs @@ -0,0 +1,78 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Caliburn.Micro; +using Jarvis.Addin.Files.Indexing; +using Jarvis.Core; + +namespace Jarvis.Addin.Files.ViewModels +{ + internal sealed class FileSettingsViewModel : Screen, ISettings + { + private readonly IEventAggregator _eventAggregator; + private bool _hasChanges; + + public string Name => "File indexing"; + + public IncludeFolderViewModel IncludeFolders { get; } + public IncludeExtensionsViewModel IncludeExtensions { get; } + public ExcludeFolderViewModel ExcludeFolders { get; } + public ExcludePatternViewModel ExcludePatterns { get; } + + public FileSettingsViewModel( + IEventAggregator eventAggregator, + IncludeFolderViewModel includeFolders, + IncludeExtensionsViewModel includeExtensions, + ExcludeFolderViewModel excludeFolders, + ExcludePatternViewModel excludePatterns) + { + _eventAggregator = eventAggregator; + IncludeFolders = includeFolders; + IncludeExtensions = includeExtensions; + ExcludeFolders = excludeFolders; + ExcludePatterns = excludePatterns; + } + + public ValidationResult Validate() + { + return ValidationResult.Ok(); + } + + public void Load(ISettingsStore settings) + { + var model = FileSettings.Load(settings); + + IncludeFolders.Load(model); + IncludeExtensions.Load(model); + ExcludeFolders.Load(model); + ExcludePatterns.Load(model); + } + + public void Save(ISettingsStore settings) + { + var model = new FileSettings(); + + IncludeFolders.Populate(ref model); + IncludeExtensions.Populate(ref model); + ExcludeFolders.Populate(ref model); + ExcludePatterns.Populate(ref model); + + model.Save(settings); + + if (!_hasChanges) + { + _hasChanges = IncludeFolders.IsDirty || IncludeExtensions.IsDirty + || ExcludeFolders.IsDirty || ExcludePatterns.IsDirty; + } + } + + public void OnSaved() + { + if (_hasChanges) + { + _eventAggregator.PublishOnCurrentThread(new TriggerIndexMessage()); + } + } + } +} diff --git a/src/Jarvis.Addin.Files/ViewModels/IncludeExtensionsViewModel.cs b/src/Jarvis.Addin.Files/ViewModels/IncludeExtensionsViewModel.cs new file mode 100644 index 0000000..ba664e8 --- /dev/null +++ b/src/Jarvis.Addin.Files/ViewModels/IncludeExtensionsViewModel.cs @@ -0,0 +1,44 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Linq; +using Jarvis.Core; + +namespace Jarvis.Addin.Files.ViewModels +{ + internal sealed class IncludeExtensionsViewModel : SelectPatternViewModel + { + public override ValidationResult Validate() + { + return ValidationResult.Ok(); + } + + public override string Process(string value) + { + return value?.TrimStart('.').Trim(); + } + + public override bool IsValid(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + var invalidCharacters = Path.GetInvalidFileNameChars(); + return value.All(character => !invalidCharacters.Contains(character)); + } + + public override void Load(FileSettings settings) + { + Items.AddRange(settings.IncludedExtensions); + } + + public override void Populate(ref FileSettings settings) + { + settings.IncludedExtensions.Clear(); + settings.IncludedExtensions.AddRange(Items); + } + } +} \ No newline at end of file diff --git a/src/Jarvis.Addin.Files/ViewModels/IncludeFolderViewModel.cs b/src/Jarvis.Addin.Files/ViewModels/IncludeFolderViewModel.cs new file mode 100644 index 0000000..1de7fb7 --- /dev/null +++ b/src/Jarvis.Addin.Files/ViewModels/IncludeFolderViewModel.cs @@ -0,0 +1,29 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Jarvis.Core; + +namespace Jarvis.Addin.Files.ViewModels +{ + internal sealed class IncludeFolderViewModel : SelectFolderViewModel + { + protected override string Description => "Select a folder to index."; + + public override ValidationResult Validate() + { + return ValidationResult.Ok(); + } + + public override void Load(FileSettings settings) + { + Items.AddRange(settings.IncludedFolders); + } + + public override void Populate(ref FileSettings settings) + { + settings.IncludedFolders.Clear(); + settings.IncludedFolders.AddRange(Items); + } + } +} \ No newline at end of file diff --git a/src/Jarvis.Addin.Files/ViewModels/SelectFolderViewModel.cs b/src/Jarvis.Addin.Files/ViewModels/SelectFolderViewModel.cs new file mode 100644 index 0000000..cdd2670 --- /dev/null +++ b/src/Jarvis.Addin.Files/ViewModels/SelectFolderViewModel.cs @@ -0,0 +1,53 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Windows.Forms; +using Caliburn.Micro; +using Jarvis.Core; +using Spectre.System.IO; + +namespace Jarvis.Addin.Files.ViewModels +{ + internal abstract class SelectFolderViewModel : Conductor.Collection.OneActive + { + public bool CanRemoveFolder => ActiveItem != null; + protected abstract string Description { get; } + public bool IsDirty { get; private set; } + + public void Toggle(DirectoryPath item) + { + NotifyOfPropertyChange(nameof(CanRemoveFolder)); + } + + public void AddFolder() + { + using (var dialog = new FolderBrowserDialog()) + { + dialog.Description = Description; + + var result = dialog.ShowDialog(); + if (result == DialogResult.OK) + { + Items.Add(new DirectoryPath(dialog.SelectedPath)); + IsDirty = true; + } + } + } + + public void RemoveFolder() + { + if (ActiveItem != null) + { + Items.Remove(ActiveItem); + ActiveItem = null; + NotifyOfPropertyChange(nameof(CanRemoveFolder)); + IsDirty = true; + } + } + + public abstract ValidationResult Validate(); + public abstract void Load(FileSettings settings); + public abstract void Populate(ref FileSettings settings); + } +} \ No newline at end of file diff --git a/src/Jarvis.Addin.Files/ViewModels/SelectPatternViewModel.cs b/src/Jarvis.Addin.Files/ViewModels/SelectPatternViewModel.cs new file mode 100644 index 0000000..cfc6480 --- /dev/null +++ b/src/Jarvis.Addin.Files/ViewModels/SelectPatternViewModel.cs @@ -0,0 +1,69 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Caliburn.Micro; +using Jarvis.Core; + +namespace Jarvis.Addin.Files.ViewModels +{ + internal abstract class SelectPatternViewModel : Conductor.Collection.OneActive + { + private string _pattern; + + public string Pattern + { + get => _pattern; + set + { + _pattern = value; + NotifyOfPropertyChange(nameof(Pattern)); + NotifyOfPropertyChange(nameof(CanAddPattern)); + NotifyOfPropertyChange(nameof(CanRemovePattern)); + } + } + + public bool CanRemovePattern => ActiveItem != null && Items.Contains(ActiveItem); + public bool CanAddPattern + { + get + { + var processedPattern = Process(Pattern); + return IsValid(processedPattern) && !Items.Contains(processedPattern); + } + } + + public bool IsDirty { get; private set; } + + public void Toggle(string item) + { + NotifyOfPropertyChange(nameof(CanRemovePattern)); + } + + public void AddPattern() + { + if (CanAddPattern) + { + Items.Add(Process(Pattern)); + Pattern = string.Empty; + IsDirty = true; + } + } + + public void RemovePattern() + { + if (CanRemovePattern) + { + Items.Remove(ActiveItem); + NotifyOfPropertyChange(nameof(CanRemovePattern)); + IsDirty = true; + } + } + + public abstract ValidationResult Validate(); + public abstract string Process(string value); + public abstract bool IsValid(string value); + public abstract void Load(FileSettings settings); + public abstract void Populate(ref FileSettings settings); + } +} \ No newline at end of file diff --git a/src/Jarvis.Addin.Files/Views/ExcludeFolderView.xaml b/src/Jarvis.Addin.Files/Views/ExcludeFolderView.xaml new file mode 100644 index 0000000..5abc0df --- /dev/null +++ b/src/Jarvis.Addin.Files/Views/ExcludeFolderView.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + - - - + + + + + + + + + + + + + From af95dbef1ebf3523f974b5b9771b6a23983e1c1d Mon Sep 17 00:00:00 2001 From: Jan Karger Date: Sat, 13 Jan 2018 12:21:04 +0100 Subject: [PATCH 4/8] Add hidden visibility window settings to DisplayRootViewFor to prevent flickering the window on startup --- src/Jarvis/Bootstrapper.cs | 4 +++- src/Jarvis/Views/ShellView.xaml | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Jarvis/Bootstrapper.cs b/src/Jarvis/Bootstrapper.cs index fa9672f..750f315 100644 --- a/src/Jarvis/Bootstrapper.cs +++ b/src/Jarvis/Bootstrapper.cs @@ -61,7 +61,9 @@ protected override void OnStartup(object sender, StartupEventArgs e) } // Show the root view. - DisplayRootViewFor(); + Dictionary window_settings = new Dictionary(); + window_settings.Add("Visibility", System.Windows.Visibility.Hidden); + DisplayRootViewFor(window_settings); Application?.MainWindow?.Hide(); // Create the taskbar icon. diff --git a/src/Jarvis/Views/ShellView.xaml b/src/Jarvis/Views/ShellView.xaml index 4d62ac8..5331307 100644 --- a/src/Jarvis/Views/ShellView.xaml +++ b/src/Jarvis/Views/ShellView.xaml @@ -11,7 +11,8 @@ ResizeMode="NoResize" AllowsTransparency="True" ShowActivated="False" - Topmost="False" Visibility="Hidden" + Topmost="False" + Visibility="Hidden" cal:Message.Attach="[Event Deactivated] = [Action OnDeactivated()]; [Event Closing] = [Action OnClose($eventArgs)]" WindowStyle="None"> @@ -69,7 +70,7 @@ - + From 9dbddc2aa354c30e274c5160b12d70799b4ecad6 Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Sun, 14 Jan 2018 02:20:53 +0100 Subject: [PATCH 5/8] Added option to install update from within Jarvis. Closes #53 --- src/Jarvis/Bootstrapper.cs | 8 +- src/Jarvis/Bootstrapping/AddinModule.cs | 78 +++++++++++++++++ src/Jarvis/Bootstrapping/JarvisModule.cs | 83 ++----------------- src/Jarvis/Bootstrapping/UpdaterModule.cs | 25 ++++++ .../Infrastructure/Utilities/PathUtility.cs | 3 + src/Jarvis/Jarvis.csproj | 3 + .../Services/Updating/FakeUpdateChecker.cs | 4 +- .../Services/Updating/GitHubReleaseAsset.cs | 17 ++++ .../Services/Updating/GitHubReleases.cs | 13 ++- .../Services/Updating/JarvisUpdateInfo.cs | 4 +- src/Jarvis/ViewModels/UpdateViewModel.cs | 69 ++++++++++++++- src/Jarvis/Views/UpdateView.xaml | 44 ++++++++-- 12 files changed, 262 insertions(+), 89 deletions(-) create mode 100644 src/Jarvis/Bootstrapping/AddinModule.cs create mode 100644 src/Jarvis/Bootstrapping/UpdaterModule.cs create mode 100644 src/Jarvis/Services/Updating/GitHubReleaseAsset.cs diff --git a/src/Jarvis/Bootstrapper.cs b/src/Jarvis/Bootstrapper.cs index 750f315..a360fe6 100644 --- a/src/Jarvis/Bootstrapper.cs +++ b/src/Jarvis/Bootstrapper.cs @@ -38,11 +38,15 @@ protected override void Configure() // Configure container. var builder = new ContainerBuilder(); builder.RegisterInstance(Application).As(); - builder.RegisterModule(new JarvisModule( + builder.RegisterModule(new JarvisModule()); + builder.RegisterModule(); + builder.RegisterModule(); + + // Configure addins. + builder.RegisterModule(new AddinModule( typeof(FileAddin).Assembly, typeof(GoogleAddin).Assembly, typeof(WikipediaAddin).Assembly)); - builder.RegisterModule(); // Build the container. _container = builder.Build(); diff --git a/src/Jarvis/Bootstrapping/AddinModule.cs b/src/Jarvis/Bootstrapping/AddinModule.cs new file mode 100644 index 0000000..74954b8 --- /dev/null +++ b/src/Jarvis/Bootstrapping/AddinModule.cs @@ -0,0 +1,78 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using Autofac; +using Caliburn.Micro; +using Jarvis.Core; +using Module = Autofac.Module; + +namespace Jarvis.Bootstrapping +{ + public sealed class AddinModule : Module + { + private readonly Assembly[] _assemblies; + + public AddinModule(params Assembly[] assemblies) + { + _assemblies = assemblies; + } + + protected override void Load(ContainerBuilder builder) + { + var assemblies = new HashSet(AppDomain.CurrentDomain.GetAssemblies().Concat(_assemblies)); + foreach (var assembly in assemblies) + { + var modules = GetModulesInAssembly(assembly).ToArray(); + foreach (var module in modules) + { + module.Configure(builder); + } + + if (modules.Length > 0) + { + // Make sure Caliburn.Micro can find external views. + if (!AssemblySource.Instance.Contains(assembly)) + { + AssemblySource.Instance.Add(assembly); + } + + // View models + builder.RegisterAssemblyTypes(assembly) + .Where(type => type.Name.EndsWith("ViewModel", StringComparison.Ordinal)) + .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name) != null) + .AsSelf().InstancePerDependency(); + + // Register Views + builder.RegisterAssemblyTypes(assembly) + .Where(type => type.Name.EndsWith("View", StringComparison.Ordinal)) + .AsSelf().InstancePerDependency(); + } + } + } + + private static IEnumerable GetModulesInAssembly(Assembly assembly) + { + foreach (var attribute in assembly.GetCustomAttributes()) + { + if (!typeof(IAddin).IsAssignableFrom(attribute.ModuleType)) + { + continue; + } + + var defaultConstructor = attribute.ModuleType.GetConstructor(Type.EmptyTypes); + if (defaultConstructor == null) + { + throw new InvalidOperationException("Could not find default constructor."); + } + + yield return Activator.CreateInstance(attribute.ModuleType) as IAddin; + } + } + } +} diff --git a/src/Jarvis/Bootstrapping/JarvisModule.cs b/src/Jarvis/Bootstrapping/JarvisModule.cs index 9184041..bfdd864 100644 --- a/src/Jarvis/Bootstrapping/JarvisModule.cs +++ b/src/Jarvis/Bootstrapping/JarvisModule.cs @@ -3,17 +3,13 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using System.Reflection; using Autofac; using Caliburn.Micro; using Jarvis.Bootstrapping.Seeding; using Jarvis.Core; using Jarvis.Core.Threading; using Jarvis.Services; -using Jarvis.Services.Updating; using Jarvis.ViewModels; using Spectre.System.IO; using Module = Autofac.Module; @@ -22,13 +18,6 @@ namespace Jarvis.Bootstrapping { public sealed class JarvisModule : Module { - private readonly Assembly[] _assemblies; - - public JarvisModule(params Assembly[] assemblies) - { - _assemblies = assemblies; - } - protected override void Load(ContainerBuilder builder) { // Caliburn.Micro @@ -42,16 +31,6 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().SingleInstance(); builder.RegisterType().As().SingleInstance(); -#if !DEBUG || FAKERELEASE - builder.RegisterType().As().SingleInstance(); -#else -#if HAS_UPDATE - builder.RegisterType().As().SingleInstance(); -#else - builder.RegisterType().As().SingleInstance(); -#endif -#endif - // Services builder.RegisterType().SingleInstance(); builder.RegisterType().SingleInstance(); @@ -65,60 +44,16 @@ protected override void Load(ContainerBuilder builder) // Seeders builder.RegisterType().As().SingleInstance(); - // Register view models and views. - foreach (var assembly in _assemblies.Concat(new[] { typeof(JarvisModule).Assembly })) - { - // View models - builder.RegisterAssemblyTypes(assembly) - .Where(type => type.Name.EndsWith("ViewModel", StringComparison.Ordinal)) - .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name) != null) - .AsSelf().InstancePerDependency(); - - // Register Views - builder.RegisterAssemblyTypes(assembly) - .Where(type => type.Name.EndsWith("View", StringComparison.Ordinal)) - .AsSelf().InstancePerDependency(); - } - - // Load addins. - LoadAddins(builder); - } - - private void LoadAddins(ContainerBuilder builder) - { - var assemblies = new HashSet(AppDomain.CurrentDomain.GetAssemblies().Concat(_assemblies)); - foreach (var assembly in assemblies) - { - foreach (var module in GetModulesInAssembly(assembly)) - { - module.Configure(builder); - } - - // Make sure Caliburn.Micro can find external views. - if (!AssemblySource.Instance.Contains(assembly)) - { - AssemblySource.Instance.Add(assembly); - } - } - } - - private static IEnumerable GetModulesInAssembly(Assembly assembly) - { - foreach (var attribute in assembly.GetCustomAttributes()) - { - if (!typeof(IAddin).IsAssignableFrom(attribute.ModuleType)) - { - continue; - } - - var defaultConstructor = attribute.ModuleType.GetConstructor(Type.EmptyTypes); - if (defaultConstructor == null) - { - throw new InvalidOperationException("Could not find default constructor."); - } + // View models + builder.RegisterAssemblyTypes(typeof(JarvisModule).Assembly) + .Where(type => type.Name.EndsWith("ViewModel", StringComparison.Ordinal)) + .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name) != null) + .AsSelf().InstancePerDependency(); - yield return Activator.CreateInstance(attribute.ModuleType) as IAddin; - } + // Register Views + builder.RegisterAssemblyTypes(typeof(JarvisModule).Assembly) + .Where(type => type.Name.EndsWith("View", StringComparison.Ordinal)) + .AsSelf().InstancePerDependency(); } } } diff --git a/src/Jarvis/Bootstrapping/UpdaterModule.cs b/src/Jarvis/Bootstrapping/UpdaterModule.cs new file mode 100644 index 0000000..5570215 --- /dev/null +++ b/src/Jarvis/Bootstrapping/UpdaterModule.cs @@ -0,0 +1,25 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Autofac; +using Jarvis.Services.Updating; + +namespace Jarvis.Bootstrapping +{ + public sealed class UpdaterModule : Module + { + protected override void Load(ContainerBuilder builder) + { +#if !DEBUG || FAKERELEASE + builder.RegisterType().As().SingleInstance(); +#else +#if HAS_UPDATE + builder.RegisterType().As().SingleInstance(); +#else + builder.RegisterType().As().SingleInstance(); +#endif +#endif + } + } +} diff --git a/src/Jarvis/Infrastructure/Utilities/PathUtility.cs b/src/Jarvis/Infrastructure/Utilities/PathUtility.cs index c420378..3298e07 100644 --- a/src/Jarvis/Infrastructure/Utilities/PathUtility.cs +++ b/src/Jarvis/Infrastructure/Utilities/PathUtility.cs @@ -11,6 +11,7 @@ public static class PathUtility { public static DirectoryPath DataPath { get; set; } public static DirectoryPath LogPath { get; set; } + public static DirectoryPath InstallerPath { get; set; } public static void Initialize() { @@ -18,10 +19,12 @@ public static void Initialize() .Combine(new DirectoryPath("Jarvis")); LogPath = DataPath.Combine(new DirectoryPath("Logs")); + InstallerPath = DataPath.Combine(new DirectoryPath("Installers")); var fileSystem = new FileSystem(); CreatePath(fileSystem, DataPath); CreatePath(fileSystem, LogPath); + CreatePath(fileSystem, InstallerPath); } private static void CreatePath(IFileSystem fileSystem, DirectoryPath path) diff --git a/src/Jarvis/Jarvis.csproj b/src/Jarvis/Jarvis.csproj index fb7c376..fc2183c 100644 --- a/src/Jarvis/Jarvis.csproj +++ b/src/Jarvis/Jarvis.csproj @@ -111,7 +111,9 @@ Properties\SharedAssemblyInfo.cs + + @@ -146,6 +148,7 @@ + diff --git a/src/Jarvis/Services/Updating/FakeUpdateChecker.cs b/src/Jarvis/Services/Updating/FakeUpdateChecker.cs index 23a3642..b41b240 100644 --- a/src/Jarvis/Services/Updating/FakeUpdateChecker.cs +++ b/src/Jarvis/Services/Updating/FakeUpdateChecker.cs @@ -48,7 +48,9 @@ public async Task CheckForUpdates() CurrentVersion = _currentVersion, FutureVersion = _futureVersion, Prerelease = false, - Uri = new Uri("https://github.com/cake-build/cake/releases/tag/v0.23.0") + ReleaseUri = new Uri("https://github.com/spectresystems/jarvis/releases/tag/v0.6.0"), + InstallerName = "Jarvis-0.6.0-x64.exe", + InstallerUri = new Uri("https://github.com/spectresystems/jarvis/releases/download/v0.6.0/Jarvis-0.6.0-x64.exe") }; } } diff --git a/src/Jarvis/Services/Updating/GitHubReleaseAsset.cs b/src/Jarvis/Services/Updating/GitHubReleaseAsset.cs new file mode 100644 index 0000000..86f8128 --- /dev/null +++ b/src/Jarvis/Services/Updating/GitHubReleaseAsset.cs @@ -0,0 +1,17 @@ +// Licensed to Spectre Systems AB under one or more agreements. +// Spectre Systems AB licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; + +namespace Jarvis.Services.Updating +{ + public class GitHubReleaseAsset + { + [JsonProperty("name")] + public string FileName { get; set; } + + [JsonProperty("browser_download_url")] + public string DownloadUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Jarvis/Services/Updating/GitHubReleases.cs b/src/Jarvis/Services/Updating/GitHubReleases.cs index 888a78b..c4c4d5b 100644 --- a/src/Jarvis/Services/Updating/GitHubReleases.cs +++ b/src/Jarvis/Services/Updating/GitHubReleases.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; namespace Jarvis.Services.Updating @@ -24,14 +26,21 @@ public class GitHubRelease [JsonProperty("html_url")] public string HtmlUrl { get; set; } + [JsonProperty("assets")] + public List Assets { get; set; } + public JarvisUpdateInfo ToJarvisUpdateInfo(Version currentVersion) { + var installer = Assets.FirstOrDefault(x => x.DownloadUrl?.EndsWith(".exe") ?? false); + return new JarvisUpdateInfo { CurrentVersion = currentVersion, FutureVersion = Version.Parse(Name.Trim('v')), - Uri = new Uri(HtmlUrl), - Prerelease = Prerelease + ReleaseUri = new Uri(HtmlUrl), + Prerelease = Prerelease, + InstallerName = installer?.FileName, + InstallerUri = installer?.DownloadUrl != null ? new Uri(installer.DownloadUrl) : null }; } } diff --git a/src/Jarvis/Services/Updating/JarvisUpdateInfo.cs b/src/Jarvis/Services/Updating/JarvisUpdateInfo.cs index 9681c96..963d417 100644 --- a/src/Jarvis/Services/Updating/JarvisUpdateInfo.cs +++ b/src/Jarvis/Services/Updating/JarvisUpdateInfo.cs @@ -11,6 +11,8 @@ public sealed class JarvisUpdateInfo public Version CurrentVersion { get; set; } public Version FutureVersion { get; set; } public bool Prerelease { get; set; } - public Uri Uri { get; set; } + public Uri ReleaseUri { get; set; } + public string InstallerName { get; set; } + public Uri InstallerUri { get; set; } } } \ No newline at end of file diff --git a/src/Jarvis/ViewModels/UpdateViewModel.cs b/src/Jarvis/ViewModels/UpdateViewModel.cs index 8f0af5f..ff4ef15 100644 --- a/src/Jarvis/ViewModels/UpdateViewModel.cs +++ b/src/Jarvis/ViewModels/UpdateViewModel.cs @@ -3,33 +3,96 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; using Caliburn.Micro; +using Jarvis.Infrastructure.Utilities; using Jarvis.Services.Updating; using JetBrains.Annotations; +using Spectre.System.IO; namespace Jarvis.ViewModels { public sealed class UpdateViewModel : Screen { private readonly JarvisUpdateInfo _update; + private readonly IFileSystem _fileSystem; + private bool _isInstalling; public delegate UpdateViewModel Factory(JarvisUpdateInfo info); [UsedImplicitly] public string Version => _update.FutureVersion.ToString(); [UsedImplicitly] - public string Url => _update.Uri.AbsoluteUri; + public string Url => _update.ReleaseUri.AbsoluteUri; - public UpdateViewModel(JarvisUpdateInfo info) + [UsedImplicitly] + public bool CanInstall => !IsInstalling && _update.InstallerUri != null; + [UsedImplicitly] + public bool CanCloseDialog => !IsInstalling; + + [UsedImplicitly] + public bool IsInstalling + { + get => _isInstalling; + set + { + _isInstalling = value; + NotifyOfPropertyChange(nameof(IsInstalling)); + NotifyOfPropertyChange(nameof(CanInstall)); + NotifyOfPropertyChange(nameof(CanCloseDialog)); + } + } + + public UpdateViewModel(JarvisUpdateInfo info, IFileSystem fileSystem) { _update = info; + _fileSystem = fileSystem; } [UsedImplicitly] public void OpenBrowser() { Process.Start(Url); - TryClose(true); + } + + [UsedImplicitly] + public async Task Install() + { + try + { + // Get the installer path. + var installerPath = PathUtility.InstallerPath.CombineWithFilePath(new FilePath(_update.InstallerName)); + if (!_fileSystem.Exist(installerPath)) + { + IsInstalling = true; + + // Download asset. + using (var client = new HttpClient()) + { + var response = await client.GetAsync(_update.InstallerUri); + if (response.IsSuccessStatusCode) + { + using (var contentStream = await response.Content.ReadAsStreamAsync()) + using (var fileStream = new FileStream(installerPath.FullPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) + { + await contentStream.CopyToAsync(fileStream); + } + } + } + } + + // Launch the installer. + Process.Start(installerPath.FullPath); + + // Close the dialog. + TryClose(true); + } + finally + { + IsInstalling = false; + } } [UsedImplicitly] diff --git a/src/Jarvis/Views/UpdateView.xaml b/src/Jarvis/Views/UpdateView.xaml index 1dd8493..514033b 100644 --- a/src/Jarvis/Views/UpdateView.xaml +++ b/src/Jarvis/Views/UpdateView.xaml @@ -3,12 +3,14 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" + xmlns:cal="http://www.caliburnproject.org" mc:Ignorable="d" Topmost="True" WindowStyle="SingleBorderWindow" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" - SizeToContent="WidthAndHeight" - Title="Update available"> + SizeToContent="WidthAndHeight" Width="300" + Title="Update available" d:DataContext="{d:DesignData UpdateViewModel}"> @@ -17,14 +19,44 @@ + A new version of Jarvis is available: - + + + + + + + + + + - -