From 061c6415b8a58ac1e52debc9f8338768c317c20f Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 24 Apr 2024 14:41:02 -0400 Subject: [PATCH] 1226 select notebooks (#1355) * select notebooks #1226 --- OneMore/AddInCommands.cs | 5 + .../Commands/Page/ArrangeContainersCommand.cs | 69 +++-- OneMore/Commands/Settings/ContextMenuSheet.cs | 5 +- OneMore/Commands/Settings/HashtagSheet.cs | 48 +--- OneMore/Commands/Tagging/HashtagCommand.cs | 28 +-- .../Tagging/HashtagDialog.Designer.cs | 16 +- OneMore/Commands/Tagging/HashtagDialog.cs | 37 +++ OneMore/Commands/Tagging/HashtagProvider.cs | 173 +++++++++++-- .../Commands/Tagging/HashtagScanCommand.cs | 72 ++++++ OneMore/Commands/Tagging/HashtagScanner.cs | 77 ++++-- OneMore/Commands/Tagging/HashtagScheduler.cs | 13 + OneMore/Commands/Tagging/HashtagService.cs | 17 ++ OneMore/Commands/Tagging/HashtagsDB.sql | 3 +- .../Tagging/LegacyTaggingConverter.cs | 23 +- .../Tagging/ScheduleScanDialog.Designer.cs | 189 +++++++++++++- .../Commands/Tagging/ScheduleScanDialog.cs | 238 +++++++++++++++++- OneMore/Helpers/Logger.cs | 2 +- OneMore/Models/MetaNames.cs | 1 + OneMore/OneMore.csproj | 1 + OneMore/Properties/Resources.Designer.cs | 45 ++++ OneMore/Properties/Resources.ar-SA.resx | 17 ++ OneMore/Properties/Resources.de-DE.resx | 17 ++ OneMore/Properties/Resources.es-ES.resx | 17 ++ OneMore/Properties/Resources.fr-FR.resx | 17 ++ OneMore/Properties/Resources.he-IL.resx | 17 ++ OneMore/Properties/Resources.nl-NL.resx | 17 ++ OneMore/Properties/Resources.pl-PL.resx | 17 ++ OneMore/Properties/Resources.pt-BR.resx | 17 ++ OneMore/Properties/Resources.resx | 17 ++ OneMore/Properties/Resources.zh-CN.resx | 17 ++ OneMore/UI/MoreListView.cs | 6 + OneMoreTray/App.config | 9 +- OneMoreTray/ScanningJob.cs | 13 +- 33 files changed, 1128 insertions(+), 132 deletions(-) create mode 100644 OneMore/Commands/Tagging/HashtagScanCommand.cs diff --git a/OneMore/AddInCommands.cs b/OneMore/AddInCommands.cs index a94800397e..8fb08231a4 100644 --- a/OneMore/AddInCommands.cs +++ b/OneMore/AddInCommands.cs @@ -744,6 +744,11 @@ public async Task SaveSnippetCmd(IRibbonControl control) => await factory.Run(); + [Command("ribScheduleHashtagScanButton_Label", Keys.None, "ribSearchMenu")] + public async Task ScheduleHashtagScanCmd(IRibbonControl control) + => await factory.Run(); + + [Command("ribSearchButton_Label", Keys.None, "ribSearchMenu")] public async Task SearchCmd(IRibbonControl control) => await factory.Run(); diff --git a/OneMore/Commands/Page/ArrangeContainersCommand.cs b/OneMore/Commands/Page/ArrangeContainersCommand.cs index 037f741d69..2555b1ce98 100644 --- a/OneMore/Commands/Page/ArrangeContainersCommand.cs +++ b/OneMore/Commands/Page/ArrangeContainersCommand.cs @@ -1,5 +1,5 @@ //************************************************************************************************ -// Copyright © 2021 Steven M Cohn. All rights reserved. +// Copyright © 2021 Steven M Cohn. All rights reserved. //************************************************************************************************ namespace River.OneMoreAddIn.Commands @@ -78,7 +78,7 @@ private void FindTopMargin() .Select(e => e.Parent) .FirstOrDefault(); - if (bank == null) + if (bank is null) { topMargin = TopMargin; } @@ -190,35 +190,66 @@ private bool ArrangeFlow(int columns, int pageWidth) } - // Collects a list of containers that have content, filtering out those with - // empty text runs. OneNote tends to append an empty container after Update regardless + // Collects a list of Outlines that have content, removing the last Outline if it + // only has a single line of whitespace. OneNote sometimes appends an empty container + // after Update for some reason. private IEnumerable CollectContainers(Page page, XNamespace ns) { - var containers = page.Root.Elements(ns + "Outline") + var outlines = page.Root.Elements(ns + "Outline") .Where(e => !e.Elements(ns + "Meta") .Any(m => m.Attribute("name").Value == MetaNames.TaggingBank)) .ToList(); - foreach (var container in containers) + if (outlines.Count < 2) + { + // zero or one Outline; don't leave the page entirely empty + return outlines; + } + + // we only care about the last Outline... + + var index = outlines.Count - 1; + var last = outlines[index]; + var blocks = last.Descendants(ns + "HTMLBlock"); + if (blocks.Any()) + { + // HTMLBlock is content + return outlines; + } + + var paragraphs = last.Descendants(ns + "OE"); + if (!paragraphs.Any()) { - var runs = container.Descendants(ns + "T"); - if (runs.Any()) + // remove empty Outline + outlines.RemoveAt(index); + return outlines; + } + + // if Outline contains exactly one paragraph + if (paragraphs.Count() == 1 && + paragraphs.First() is XElement paragraph) + { + var descendants = paragraph.Descendants(); + + // contains Meta, Image, Table, or other non-text elements + if (descendants.Any(e => e.Name.LocalName != "T")) { - var text = runs.Nodes().OfType() - .Select(c => c.Value.Trim()) - .Aggregate((a, b) => $"{a}{b}"); - - if (text.Length > 0) - { - yield return container; - } + return outlines; } - else + + var text = descendants + .Where(e => e.Name.LocalName == "T") + .Nodes().OfType() + .Select(c => c.Value.Trim()) + .Aggregate((a, b) => $"{a}{b}"); + + if (text?.Length == 0) { - // likely contains an InsertedFile or image without text runs - yield return container; + outlines.RemoveAt(index); } } + + return outlines; } } } diff --git a/OneMore/Commands/Settings/ContextMenuSheet.cs b/OneMore/Commands/Settings/ContextMenuSheet.cs index b172d7ef4d..6425d23d46 100644 --- a/OneMore/Commands/Settings/ContextMenuSheet.cs +++ b/OneMore/Commands/Settings/ContextMenuSheet.cs @@ -139,9 +139,8 @@ private MenuItemPanel(string name, string resID) { name = Resx.ResourceManager.GetString(resID, AddIn.Culture) ?? name; - using var graphics = Graphics.FromHwnd(IntPtr.Zero); - var textSize = graphics.MeasureString(name, Font); - Height = (int)(textSize.Height + 6); + var textSize = TextRenderer.MeasureText(name, Font); + Height = textSize.Height + 6; box = new UI.MoreCheckBox { diff --git a/OneMore/Commands/Settings/HashtagSheet.cs b/OneMore/Commands/Settings/HashtagSheet.cs index 971be5d9fe..28f042cb2b 100644 --- a/OneMore/Commands/Settings/HashtagSheet.cs +++ b/OneMore/Commands/Settings/HashtagSheet.cs @@ -6,6 +6,7 @@ namespace River.OneMoreAddIn.Settings { using River.OneMoreAddIn.Commands; using River.OneMoreAddIn.Styles; + using System; using System.Linq; using System.Windows.Forms; using Resx = Properties.Resources; @@ -13,8 +14,6 @@ namespace River.OneMoreAddIn.Settings internal partial class HashtagSheet : SheetBase { - private readonly HashtagScheduler scheduler; - public HashtagSheet(SettingsProvider provider) : base(provider) { @@ -77,8 +76,6 @@ public HashtagSheet(SettingsProvider provider) : base(provider) filterBox.Checked = settings.Get("unfiltered"); - upgradeLink.Enabled = !provider.GetCollection("tagging").Get("converted", false); - if (provider.GetCollection("GeneralSheet").Get("experimental", false)) { delayBox.Value = settings.Get("delay", HashtagScanner.DefaultThrottle); @@ -89,43 +86,24 @@ public HashtagSheet(SettingsProvider provider) : base(provider) delayBox.Visible = false; msLabel.Visible = false; } - - scheduler = new HashtagScheduler(); } - private async void ScheduleRebuild(object sender, LinkLabelLinkClickedEventArgs e) + protected override async void OnLoad(EventArgs e) { - using var dialog = - scheduler.State == ScanningState.None || - scheduler.State == ScanningState.Ready - ? new ScheduleScanDialog() - : new ScheduleScanDialog(scheduler.StartTime); - - if (scheduler.State != ScanningState.None && - scheduler.State != ScanningState.Ready) - { - dialog.SetIntroText(string.Format( - Resx.HashtagSheet_prescheduled, - scheduler.StartTime.ToString("ddd, MMMM d, yyyy h:mm tt")) - ); - } - else - { - dialog.SetIntroText(Resx.HashtagSheet_scanNotebooks); - } + base.OnLoad(e); - // - // FULL? - // + var converter = new LegacyTaggingConverter(); + upgradeLink.Enabled = await converter.NeedsConversion(); + } - var result = dialog.ShowDialog(this); - if (result == DialogResult.OK) - { - scheduler.StartTime = dialog.StartTime; - scheduler.State = ScanningState.PendingScan; - await scheduler.Activate(); - } + + private async void ScheduleRebuild(object sender, LinkLabelLinkClickedEventArgs e) + { + var cmd = new HashtagScanCommand(); + cmd.SetLogger(logger); + cmd.SetOwner(this); + await cmd.Execute(); } diff --git a/OneMore/Commands/Tagging/HashtagCommand.cs b/OneMore/Commands/Tagging/HashtagCommand.cs index 31b65d340c..a3b18f3536 100644 --- a/OneMore/Commands/Tagging/HashtagCommand.cs +++ b/OneMore/Commands/Tagging/HashtagCommand.cs @@ -4,7 +4,6 @@ namespace River.OneMoreAddIn.Commands { - using River.OneMoreAddIn.UI; using System; using System.Collections.Generic; using System.Linq; @@ -78,14 +77,15 @@ private async Task ConfirmReady() if (!HashtagProvider.DatabaseExists()) { - using var scheduleDialog = scheduler.State == ScanningState.None - ? new ScheduleScanDialog() - : new ScheduleScanDialog(scheduler.StartTime); + using var sdialog = scheduler.State == ScanningState.None + ? new ScheduleScanDialog(false) + : new ScheduleScanDialog(false, scheduler.StartTime); - var result = scheduleDialog.ShowDialog(owner); + var result = sdialog.ShowDialog(owner); if (result == DialogResult.OK) { - scheduler.StartTime = scheduleDialog.StartTime; + scheduler.Notebooks = sdialog.GetSelectedNotebooks(); + scheduler.StartTime = sdialog.StartTime; scheduler.State = ScanningState.PendingRebuild; await scheduler.Activate(); } @@ -93,15 +93,15 @@ private async Task ConfirmReady() return false; } - if (scheduler.State != ScanningState.Ready) - { - var msg = scheduler.State == ScanningState.Scanning - ? Resx.HashtagCommand_scanning - : string.Format(Resx.HashtagCommand_waiting, scheduler.StartTime.ToFriendlyString()); + //if (scheduler.State != ScanningState.Ready) + //{ + // var msg = scheduler.State == ScanningState.Scanning + // ? Resx.HashtagCommand_scanning + // : string.Format(Resx.HashtagCommand_waiting, scheduler.StartTime.ToFriendlyString()); - MoreMessageBox.Show(owner, msg); - return false; - } + // MoreMessageBox.Show(owner, msg); + // return false; + //} return true; } diff --git a/OneMore/Commands/Tagging/HashtagDialog.Designer.cs b/OneMore/Commands/Tagging/HashtagDialog.Designer.cs index 9a9db7ed6a..856d52d3a6 100644 --- a/OneMore/Commands/Tagging/HashtagDialog.Designer.cs +++ b/OneMore/Commands/Tagging/HashtagDialog.Designer.cs @@ -48,6 +48,7 @@ private void InitializeComponent() this.moveButton = new River.OneMoreAddIn.UI.MoreButton(); this.contextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); this.scanButton = new System.Windows.Forms.ToolStripMenuItem(); + this.scheduleButton = new System.Windows.Forms.ToolStripMenuItem(); this.topPanel.SuspendLayout(); this.controlPanel.SuspendLayout(); this.contextMenu.SuspendLayout(); @@ -330,17 +331,25 @@ private void InitializeComponent() // this.contextMenu.ImageScalingSize = new System.Drawing.Size(24, 24); this.contextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.scanButton}); + this.scanButton, + this.scheduleButton}); this.contextMenu.Name = "contextMenu"; - this.contextMenu.Size = new System.Drawing.Size(164, 36); + this.contextMenu.Size = new System.Drawing.Size(241, 101); // // scanButton // this.scanButton.Name = "scanButton"; - this.scanButton.Size = new System.Drawing.Size(163, 32); + this.scanButton.Size = new System.Drawing.Size(240, 32); this.scanButton.Text = "Scan Now"; this.scanButton.Click += new System.EventHandler(this.ScanNow); // + // scheduleButton + // + this.scheduleButton.Name = "scheduleButton"; + this.scheduleButton.Size = new System.Drawing.Size(240, 32); + this.scheduleButton.Text = "Schedule Scan"; + this.scheduleButton.Click += new System.EventHandler(this.DoScheduleScan); + // // HashtagDialog // this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); @@ -386,5 +395,6 @@ private void InitializeComponent() private System.Windows.Forms.ContextMenuStrip contextMenu; private System.Windows.Forms.ToolStripMenuItem scanButton; private UI.MoreMultilineLabel introBox; + private System.Windows.Forms.ToolStripMenuItem scheduleButton; } } \ No newline at end of file diff --git a/OneMore/Commands/Tagging/HashtagDialog.cs b/OneMore/Commands/Tagging/HashtagDialog.cs index c271059ebd..eb577f75aa 100644 --- a/OneMore/Commands/Tagging/HashtagDialog.cs +++ b/OneMore/Commands/Tagging/HashtagDialog.cs @@ -222,6 +222,43 @@ private async Task SearchTags(object sender, EventArgs e) } + private void DoScheduleScan(object sender, EventArgs e) + { + var scheduler = new HashtagScheduler(); + + using var dialog = + scheduler.State == ScanningState.None || + scheduler.State == ScanningState.Ready + ? new ScheduleScanDialog(true) + : new ScheduleScanDialog(true, scheduler.StartTime); + + if (scheduler.State != ScanningState.None && + scheduler.State != ScanningState.Ready) + { + dialog.SetIntroText(string.Format( + Resx.HashtagSheet_prescheduled, + scheduler.StartTime.ToString("ddd, MMMM d, yyyy h:mm tt")) + ); + } + else + { + dialog.SetIntroText(Resx.HashtagSheet_scanNotebooks); + } + + dialog.SetPreferredIDs(scheduler.Notebooks); + + var result = dialog.ShowDialog(this); + if (result == DialogResult.OK) + { + scheduler.Notebooks = dialog.GetSelectedNotebooks(); + scheduler.StartTime = dialog.StartTime; + scheduler.State = ScanningState.PendingScan; + + Task.Run(async () => { await scheduler.Activate(); }); + } + } + + private void Control_Checked(object sender, EventArgs e) { var control = sender as HashtagContextControl; diff --git a/OneMore/Commands/Tagging/HashtagProvider.cs b/OneMore/Commands/Tagging/HashtagProvider.cs index e81574af44..5fa225ba5f 100644 --- a/OneMore/Commands/Tagging/HashtagProvider.cs +++ b/OneMore/Commands/Tagging/HashtagProvider.cs @@ -148,10 +148,10 @@ private void UpgradeDatabase() version = Upgrade1to2(con); } - //if (version == 2) - //{ - // version = Upgrade2to3(con); - //} + if (version == 2) + { + version = Upgrade2to3(con); + } } @@ -176,23 +176,59 @@ private int Upgrade1to2(SQLiteConnection con) catch (Exception exc) { logger.End(); - logger.WriteLine("error upgrading hashtag_page v2", exc); + logger.WriteLine("error creating view hashtag_hashtags", exc); + return 0; + } + + if (!UpgradeSchemaVersion(cmd, transaction, 2)) + { + return 0; + } + + try + { + transaction.Commit(); + } + catch (Exception exc) + { + logger.End(); + logger.WriteLine("error committing changes for version 2", exc); return 0; } + logger.End(); + + // new version + return 2; + } + + private int Upgrade2to3(SQLiteConnection con) + { + logger.WriteLine("upgrading database to version 3"); + logger.Start(); + + using var cmd = con.CreateCommand(); + using var transaction = con.BeginTransaction(); + try { - logger.WriteLine("updating hashtag_scanner version"); + logger.WriteLine("creating table hashtag_notebook"); + cmd.CommandType = CommandType.Text; cmd.CommandText = - $"UPDATE hashtag_scanner SET version = 2 WHERE scannerID = {ScannerID}"; + "CREATE TABLE IF NOT EXISTS hashtag_notebook " + + "(notebookID TEXT PRIMARY KEY, name TEXT)"; cmd.ExecuteNonQuery(); } catch (Exception exc) { logger.End(); - logger.WriteLine("error upgrading hashtag_page v2", exc); - transaction.Rollback(); + logger.WriteLine("error creating table hashtag_notebook", exc); + return 0; + } + + if (!UpgradeSchemaVersion(cmd, transaction, 3)) + { return 0; } @@ -203,14 +239,36 @@ private int Upgrade1to2(SQLiteConnection con) catch (Exception exc) { logger.End(); - logger.WriteLine("error committing hashtag_page v2", exc); + logger.WriteLine("error committing changes for version 3", exc); return 0; } logger.End(); // new version - return 2; + return 3; + } + + private bool UpgradeSchemaVersion( + SQLiteCommand cmd, SQLiteTransaction transaction, int version) + { + try + { + logger.WriteLine($"updating hashtag_scanner version v{version}"); + cmd.CommandText = + $"UPDATE hashtag_scanner SET version = {version} WHERE scannerID = {ScannerID}"; + + cmd.ExecuteNonQuery(); + } + catch (Exception exc) + { + logger.End(); + logger.WriteLine($"error updating hashtag_scanner version v{version}", exc); + transaction.Rollback(); + return false; + } + + return true; } #endregion UpgradeDatabase @@ -378,6 +436,34 @@ public string ReadScanTime() } + /// + /// Returns a list of known notebook IDs scanned thus far that contain tags + /// + /// A collection of strings + public List ReadKnownNotebookIDs() + { + var list = new List(); + + using var cmd = con.CreateCommand(); + cmd.CommandText = "SELECT notebookID FROM hashtag_notebook"; + + try + { + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + list.Add(reader.GetString(0)); + } + } + catch (Exception exc) + { + ReportError("error reading known notebooks", cmd, exc); + } + + return list; + } + + /// /// Returns a collection of the latest tag names. /// @@ -440,6 +526,36 @@ public Hashtags ReadPageTags(string pageID) } + /// + /// Returns a list of known notebook IDs scanned thus far that contain tags + /// + /// A collection of strings + public List ReadTaggedNotebookIDs() + { + var list = new List(); + + using var cmd = con.CreateCommand(); + cmd.CommandText = + "SELECT DISTINCT(notebookID), SUBSTR(path, 0, INSTR(SUBSTR(path,2),'/')+1) " + + "FROM hashtag_page"; + + try + { + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + list.Add(reader.GetString(0)); + } + } + catch (Exception exc) + { + ReportError("error reading tagged notebooks", cmd, exc); + } + + return list; + } + + /// /// Returns a collection of all unique tag names. /// @@ -671,14 +787,16 @@ public void UpdateSnippet(Hashtags tags) /// - /// Records the timestamp value that was initialized at construction of this class - /// instance + /// Records a notebook instance; used to capture "known" notebooks /// - public void WriteScanTime() + public void WriteNotebook(string notebookID, string name) { using var cmd = con.CreateCommand(); - cmd.CommandText = "UPDATE hashtag_scanner SET scanTime = @d WHERE scannerID = 0"; - cmd.Parameters.AddWithValue("@d", timestamp); + cmd.CommandText = "REPLACE INTO hashtag_notebook " + + "(notebookID, name) VALUES (@nid, @nam)"; + + cmd.Parameters.AddWithValue("@nid", notebookID); + cmd.Parameters.AddWithValue("@nam", name); try { @@ -686,7 +804,7 @@ public void WriteScanTime() } catch (Exception exc) { - ReportError("error writing scan time", cmd, exc); + ReportError("error writing notebook", cmd, exc); } } @@ -729,6 +847,27 @@ public void WritePageInfo( } + /// + /// Records the timestamp value that was initialized at construction of this class + /// instance + /// + public void WriteScanTime() + { + using var cmd = con.CreateCommand(); + cmd.CommandText = "UPDATE hashtag_scanner SET scanTime = @d WHERE scannerID = 0"; + cmd.Parameters.AddWithValue("@d", timestamp); + + try + { + cmd.ExecuteNonQuery(); + } + catch (Exception exc) + { + ReportError("error writing scan time", cmd, exc); + } + } + + /// /// Records the given tags. /// diff --git a/OneMore/Commands/Tagging/HashtagScanCommand.cs b/OneMore/Commands/Tagging/HashtagScanCommand.cs new file mode 100644 index 0000000000..275ff68d76 --- /dev/null +++ b/OneMore/Commands/Tagging/HashtagScanCommand.cs @@ -0,0 +1,72 @@ +//************************************************************************************************ +// Copyright © 2024 Steven M Cohn. All rights reserved. +//************************************************************************************************ + +namespace River.OneMoreAddIn.Commands +{ + using River.OneMoreAddIn.UI; + using System; + using System.Threading.Tasks; + using System.Windows.Forms; + using Resx = Properties.Resources; + + + internal class HashtagScanCommand : Command + { + + public HashtagScanCommand() + { + } + + + public override async Task Execute(params object[] args) + { + var scheduler = new HashtagScheduler(); + + if (scheduler.State == ScanningState.Rebuilding || + scheduler.State == ScanningState.Scanning) + { + var msg = scheduler.State == ScanningState.Scanning + ? Resx.HashtagCommand_scanning + : string.Format(Resx.HashtagCommand_waiting, scheduler.StartTime.ToFriendlyString()); + + MoreMessageBox.Show(owner, msg); + return; + } + + // by now, there should at least be an automated scheduled scan if no db + // but keep this here just incase + var showNotebooks = HashtagProvider.DatabaseExists(); + + using var dialog = + scheduler.State == ScanningState.None || + scheduler.State == ScanningState.Ready + ? new ScheduleScanDialog(showNotebooks) + : new ScheduleScanDialog(showNotebooks, scheduler.StartTime); + + if (scheduler.State != ScanningState.None && + scheduler.State != ScanningState.Ready) + { + dialog.SetIntroText(string.Format( + Resx.HashtagSheet_prescheduled, + scheduler.StartTime.ToString("ddd, MMMM d, yyyy h:mm tt")) + ); + } + else + { + dialog.SetIntroText(Resx.HashtagSheet_scanNotebooks); + } + + dialog.SetPreferredIDs(scheduler.Notebooks); + + var result = dialog.ShowDialog(owner); + if (result == DialogResult.OK) + { + scheduler.Notebooks = dialog.GetSelectedNotebooks(); + scheduler.StartTime = dialog.StartTime; + scheduler.State = ScanningState.PendingScan; + await scheduler.Activate(); + } + } + } +} diff --git a/OneMore/Commands/Tagging/HashtagScanner.cs b/OneMore/Commands/Tagging/HashtagScanner.cs index 46409e57b8..00bdb36d48 100644 --- a/OneMore/Commands/Tagging/HashtagScanner.cs +++ b/OneMore/Commands/Tagging/HashtagScanner.cs @@ -27,6 +27,7 @@ internal class HashtagScanner : Loggable, IDisposable private readonly int throttle; private HashtagProvider provider; private XNamespace ns; + private string[] notebookFilters; private bool disposed; @@ -49,6 +50,15 @@ public HashtagScanner() } + /// + /// A list of notebook IDs to target, used for rescans and rebuilds + /// + public void SetNotebookFilters(string[] filters) + { + notebookFilters = filters; + } + + private XElement GetStyleTemplate() { var styleIndex = settings.Get("styleIndex", 0); @@ -70,7 +80,7 @@ private XElement GetStyleTemplate() var styleName = settings.Get("styleName"); var theme = new ThemeProvider().Theme; var style = theme.GetStyles().Find(s => s.Name == styleName); - if (style != null) + if (style is not null) { style.ApplyColors = true; @@ -119,7 +129,7 @@ public void Dispose() // get all notebooks var root = await one.GetNotebooks(); - if (root == null) + if (root is null) { logger.WriteLine("error HashtagScanner one.GetNotebooks()"); return (0, 0); @@ -130,18 +140,49 @@ public void Dispose() var notebooks = root.Elements(ns + "Notebook"); if (notebooks.Any()) { + var knownNotebooks = provider.ReadKnownNotebookIDs(); + foreach (var notebook in notebooks) { // gets sections for this notebook var notebookID = notebook.Attribute("ID").Value; - var sections = await one.GetNotebook(notebookID); - if (sections != null) + var name = notebook.Attribute("name").Value; + + // Filter on two levels... + // + // knownNotebooks + // If knownNotebooks is empty, then we assume that this is the first scan + // and will allow all; otherwise we only scan known notebooks to avoid + // pulling in large data from newly added notebooks - user must schedule + // a scan to pull in those new notebooks explicitly. + // + // notebookFilters + // If notebookFilters is empty then allow any notebook that has passed the + // knownNotebook test; otherwise, the user has explicitly requested a scan + // of notebooks specified in the notebookFilters list. + // + + if (knownNotebooks.Count == 0 || + (notebookFilters is null + ? knownNotebooks.Contains(notebookID) + : notebookFilters.Contains(notebookID))) { - var (dp, tp) = await Scan( - one, sections, notebookID, $"/{notebook.Attribute("name").Value}"); + //logger.Verbose($"scanning notebook {notebookID} \"{name}\""); - dirtyPages += dp; - totalPages += tp; + var sections = await one.GetNotebook(notebookID); + if (sections is not null) + { + var (dp, tp) = await Scan(one, sections, notebookID, $"/{name}"); + + dirtyPages += dp; + totalPages += tp; + } + + provider.WriteNotebook(notebookID, name); + } + else + { + logger.Verbose($"skipping notebook {notebookID} \"{name}\""); } } } @@ -162,9 +203,9 @@ public void Dispose() var sectionRefs = parent.Elements(ns + "Section") .Where(e => - e.Attribute("isRecycleBin") == null && - e.Attribute("isInRecycleBin") == null && - e.Attribute("locked") == null); + e.Attribute("isRecycleBin") is null && + e.Attribute("isInRecycleBin") is null && + e.Attribute("locked") is null); if (sectionRefs.Any()) { @@ -172,10 +213,10 @@ public void Dispose() { // get pages for this section var section = await one.GetSection(sectionRef.Attribute("ID").Value); - if (section != null) + if (section is not null) { var pages = section.Elements(ns + "Page") - .Where(e => e.Attribute("isInRecycleBin") == null); + .Where(e => e.Attribute("isInRecycleBin") is null); if (pages.Any()) { @@ -214,9 +255,9 @@ public void Dispose() var groups = parent.Elements(ns + "SectionGroup") .Where(e => - e.Attribute("isRecycleBin") == null && - e.Attribute("isInRecycleBin") == null && - e.Attribute("locked") == null); + e.Attribute("isRecycleBin") is null && + e.Attribute("isInRecycleBin") is null && + e.Attribute("locked") is null); if (groups.Any()) { @@ -251,7 +292,7 @@ private async Task ScanPage( // avoid defect https://github.com/stevencohn/OneMore/issues/1268 // GetPage throws generic COM exception and returns null... - if (page == null) + if (page is null) { logger.WriteLine($"skipping null page {pageID} '{path}'"); return false; @@ -278,7 +319,7 @@ private async Task ScanPage( foreach (var candidate in candidates) { var found = saved.Find(s => s.Equals(candidate)); - if (found == null) + if (found is null) { discovered.Add(candidate); } diff --git a/OneMore/Commands/Tagging/HashtagScheduler.cs b/OneMore/Commands/Tagging/HashtagScheduler.cs index 5bb072a966..1ced43b74e 100644 --- a/OneMore/Commands/Tagging/HashtagScheduler.cs +++ b/OneMore/Commands/Tagging/HashtagScheduler.cs @@ -30,6 +30,11 @@ internal class HashtagScheduler : Loggable #region Supporting classes private sealed class Schedule { + // The targeted names of notebooks to scan + [JsonProperty("notebooks")] + public string[] Notebooks { get; set; } = new string[0]; + + // The scheduled time to build/rebuild the hashtag database. // Could be past or future; if past then run immediately. [JsonProperty("startTime")] @@ -74,6 +79,13 @@ public HashtagScheduler() public bool Active => File.Exists(filePath) && Process.GetProcessesByName(TrayName).Any(); + public string[] Notebooks + { + get { return schedule.Notebooks; } + set { schedule.Notebooks = value; } + } + + public bool ScheduleExists => File.Exists(filePath); @@ -179,6 +191,7 @@ public void Refresh() } else { + schedule.Notebooks = update.Notebooks; schedule.State = update.State; schedule.StartTime = update.StartTime; schedule.Shutdown = update.Shutdown; diff --git a/OneMore/Commands/Tagging/HashtagService.cs b/OneMore/Commands/Tagging/HashtagService.cs index 1881fb325b..198e2eae56 100644 --- a/OneMore/Commands/Tagging/HashtagService.cs +++ b/OneMore/Commands/Tagging/HashtagService.cs @@ -29,6 +29,7 @@ internal class HashtagService : Loggable private readonly int interval; private readonly bool disabled; + private string[] notebookFilters; private HashtagScheduler scheduler; private int hour; private int scanCount; @@ -68,6 +69,16 @@ public HashtagService(bool rebuild) public event HashtagScannedHandler OnHashtagScanned; + /// + /// Sets a list of notebookIDs to target during scan/rebuild. + /// + /// + public void SetNotebookFilters(string[] filters) + { + notebookFilters = filters; + } + + /// /// Start the service /// @@ -258,6 +269,12 @@ private async Task Scan() clock.Start(); using var scanner = new HashtagScanner(); + + if (notebookFilters is not null && notebookFilters.Length > 0) + { + scanner.SetNotebookFilters(notebookFilters); + } + var (dirtyPages, totalPages) = await scanner.Scan(); clock.Stop(); diff --git a/OneMore/Commands/Tagging/HashtagsDB.sql b/OneMore/Commands/Tagging/HashtagsDB.sql index 401d3c6db5..5acd0c1973 100644 --- a/OneMore/Commands/Tagging/HashtagsDB.sql +++ b/OneMore/Commands/Tagging/HashtagsDB.sql @@ -1,8 +1,9 @@ CREATE TABLE IF NOT EXISTS hashtag_scanner (scannerID INTEGER PRIMARY KEY UNIQUE NOT NULL, version NUMERIC (12) UNIQUE NOT NULL, scanTime TEXT NOT NULL); CREATE TABLE IF NOT EXISTS hashtag (tag TEXT NOT NULL, moreID TEXT NOT NULL, objectID TEXT NOT NULL, snippet TEXT, lastModified TEXT NOT NULL, PRIMARY KEY (tag, objectID), CONSTRAINT FK_moreID FOREIGN KEY (moreID) REFERENCES hashtag_page (moreID) ON DELETE CASCADE); CREATE TABLE IF NOT EXISTS hashtag_page (moreID PRIMARY KEY, pageID TEXT NOT NULL, titleID TEXT, notebookID TEXT NOT NULL, sectionID TEXT NOT NULL, path TEXT, name TEXT); +CREATE TABLE IF NOT EXISTS hashtag_notebook (notebookID TEXT PRIMARY KEY, name TEXT); CREATE INDEX IF NOT EXISTS IDX_moreID ON hashtag (moreID); CREATE INDEX IF NOT EXISTS IDX_pageID ON hashtag_page (pageID); CREATE INDEX IF NOT EXISTS IDX_tag ON hashtag (tag); CREATE VIEW IF NOT EXISTS page_hashtags (moreID, tags) AS SELECT t.moreID, group_concat(DISTINCT(t.tag)) AS tags FROM hashtag t GROUP BY t.moreID; -REPLACE INTO hashtag_scanner (scannerID, version, scanTime) VALUES (0, 2,'0001-01-01T00:00:00.0000Z'); +REPLACE INTO hashtag_scanner (scannerID, version, scanTime) VALUES (0, 3,'0001-01-01T00:00:00.0000Z'); diff --git a/OneMore/Commands/Tagging/LegacyTaggingConverter.cs b/OneMore/Commands/Tagging/LegacyTaggingConverter.cs index 567c2ff4b6..cca36c9275 100644 --- a/OneMore/Commands/Tagging/LegacyTaggingConverter.cs +++ b/OneMore/Commands/Tagging/LegacyTaggingConverter.cs @@ -113,6 +113,26 @@ public async Task UpgradeLegacyTags(IWin32Window owner) } + public async Task NeedsConversion() + { + var provider = new SettingsProvider(); + var settings = provider.GetCollection("tagging"); + if (settings.Get("converted", false)) + { + return false; + } + + if (settings.Get("ignore", false)) + { + // previously opted to ignore upgrade + return false; + } + + var count = await GetLegacyTagCount(); + return count > 0; + } + + private async Task GetLegacyTagCount() { await using var one = new OneNote(); @@ -178,8 +198,7 @@ private async Task UpgradeLegacyTags(ProgressDialog dialog, CancellationTo .FirstOrDefault(e => e.Attribute("name").Value == MetaNames.TaggingBank); if (bank is not null && - bank.Attribute("content") is XAttribute flag && - flag.Value == "1") + bank.Attribute("content") is XAttribute flag) { logger.WriteLine($"converting page tags on {page.PageId} \"{page.Title}\""); diff --git a/OneMore/Commands/Tagging/ScheduleScanDialog.Designer.cs b/OneMore/Commands/Tagging/ScheduleScanDialog.Designer.cs index 2a8059c355..3f4c5427df 100644 --- a/OneMore/Commands/Tagging/ScheduleScanDialog.Designer.cs +++ b/OneMore/Commands/Tagging/ScheduleScanDialog.Designer.cs @@ -36,11 +36,20 @@ private void InitializeComponent() this.hintLabel = new River.OneMoreAddIn.UI.MoreLabel(); this.scheduleLabel = new River.OneMoreAddIn.UI.MoreLabel(); this.dateTimePicker = new System.Windows.Forms.DateTimePicker(); + this.notebooksPanel = new River.OneMoreAddIn.UI.MorePanel(); + this.booksPanel = new River.OneMoreAddIn.UI.MorePanel(); + this.sep2 = new River.OneMoreAddIn.UI.MoreLabel(); + this.notebooksLabel = new River.OneMoreAddIn.UI.MoreLabel(); + this.resetLink = new River.OneMoreAddIn.UI.MoreLinkLabel(); + this.selectAllLink = new River.OneMoreAddIn.UI.MoreLinkLabel(); + this.sep1 = new River.OneMoreAddIn.UI.MoreLabel(); + this.selectNoneLink = new River.OneMoreAddIn.UI.MoreLinkLabel(); this.buttonPanel = new System.Windows.Forms.Panel(); this.cancelButton = new River.OneMoreAddIn.UI.MoreButton(); this.okButton = new River.OneMoreAddIn.UI.MoreButton(); this.introBox = new River.OneMoreAddIn.UI.MoreMultilineLabel(); this.topPanel.SuspendLayout(); + this.notebooksPanel.SuspendLayout(); this.buttonPanel.SuspendLayout(); this.SuspendLayout(); // @@ -53,13 +62,13 @@ private void InitializeComponent() this.topPanel.Controls.Add(this.hintLabel); this.topPanel.Controls.Add(this.scheduleLabel); this.topPanel.Controls.Add(this.dateTimePicker); - this.topPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.topPanel.Dock = System.Windows.Forms.DockStyle.Top; this.topPanel.ForeColor = System.Drawing.SystemColors.ControlText; - this.topPanel.Location = new System.Drawing.Point(0, 62); + this.topPanel.Location = new System.Drawing.Point(0, 83); this.topPanel.Margin = new System.Windows.Forms.Padding(0); this.topPanel.Name = "topPanel"; this.topPanel.Padding = new System.Windows.Forms.Padding(40, 40, 20, 20); - this.topPanel.Size = new System.Drawing.Size(698, 315); + this.topPanel.Size = new System.Drawing.Size(718, 305); this.topPanel.TabIndex = 4; // // warningLabel @@ -68,7 +77,7 @@ private void InitializeComponent() | System.Windows.Forms.AnchorStyles.Right))); this.warningLabel.Location = new System.Drawing.Point(70, 216); this.warningLabel.Name = "warningLabel"; - this.warningLabel.Size = new System.Drawing.Size(605, 46); + this.warningLabel.Size = new System.Drawing.Size(625, 46); this.warningLabel.TabIndex = 6; this.warningLabel.Text = "OneNote may appear sluggish while scanning. If OneNote is closed during the scan," + " it cannot be opened until the scan completes."; @@ -129,6 +138,150 @@ private void InitializeComponent() this.dateTimePicker.Size = new System.Drawing.Size(379, 26); this.dateTimePicker.TabIndex = 1; // + // notebooksPanel + // + this.notebooksPanel.BackColor = System.Drawing.SystemColors.ControlLightLight; + this.notebooksPanel.BottomBorderColor = System.Drawing.Color.Transparent; + this.notebooksPanel.BottomBorderSize = 0; + this.notebooksPanel.Controls.Add(this.booksPanel); + this.notebooksPanel.Controls.Add(this.sep2); + this.notebooksPanel.Controls.Add(this.notebooksLabel); + this.notebooksPanel.Controls.Add(this.resetLink); + this.notebooksPanel.Controls.Add(this.selectAllLink); + this.notebooksPanel.Controls.Add(this.sep1); + this.notebooksPanel.Controls.Add(this.selectNoneLink); + this.notebooksPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.notebooksPanel.Location = new System.Drawing.Point(0, 388); + this.notebooksPanel.Name = "notebooksPanel"; + this.notebooksPanel.Padding = new System.Windows.Forms.Padding(0, 0, 20, 20); + this.notebooksPanel.Size = new System.Drawing.Size(718, 260); + this.notebooksPanel.TabIndex = 25; + this.notebooksPanel.ThemedBack = "ControlLightLight"; + this.notebooksPanel.ThemedFore = null; + this.notebooksPanel.TopBorderColor = System.Drawing.SystemColors.Control; + this.notebooksPanel.TopBorderSize = 0; + // + // booksPanel + // + this.booksPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.booksPanel.BackColor = System.Drawing.SystemColors.ControlLightLight; + this.booksPanel.BottomBorderColor = System.Drawing.Color.Transparent; + this.booksPanel.BottomBorderSize = 0; + this.booksPanel.Location = new System.Drawing.Point(31, 46); + this.booksPanel.Name = "booksPanel"; + this.booksPanel.Size = new System.Drawing.Size(656, 191); + this.booksPanel.TabIndex = 21; + this.booksPanel.ThemedBack = null; + this.booksPanel.ThemedFore = null; + this.booksPanel.TopBorderColor = System.Drawing.SystemColors.Control; + this.booksPanel.TopBorderSize = 0; + // + // sep2 + // + this.sep2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.sep2.AutoSize = true; + this.sep2.Location = new System.Drawing.Point(614, 16); + this.sep2.Name = "sep2"; + this.sep2.Size = new System.Drawing.Size(14, 20); + this.sep2.TabIndex = 24; + this.sep2.Text = "|"; + this.sep2.ThemedBack = null; + this.sep2.ThemedFore = null; + // + // notebooksLabel + // + this.notebooksLabel.AutoSize = true; + this.notebooksLabel.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.notebooksLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(115)))), ((int)(((byte)(53)))), ((int)(((byte)(110))))); + this.notebooksLabel.Location = new System.Drawing.Point(25, 6); + this.notebooksLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 5); + this.notebooksLabel.Name = "notebooksLabel"; + this.notebooksLabel.Size = new System.Drawing.Size(131, 32); + this.notebooksLabel.TabIndex = 18; + this.notebooksLabel.Text = "Notebooks"; + this.notebooksLabel.ThemedBack = null; + this.notebooksLabel.ThemedFore = null; + // + // resetLink + // + this.resetLink.ActiveLinkColor = System.Drawing.Color.DarkOrchid; + this.resetLink.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.resetLink.AutoSize = true; + this.resetLink.Cursor = System.Windows.Forms.Cursors.Hand; + this.resetLink.HoverColor = System.Drawing.Color.MediumOrchid; + this.resetLink.LinkColor = System.Drawing.SystemColors.ControlDark; + this.resetLink.Location = new System.Drawing.Point(635, 16); + this.resetLink.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.resetLink.MaximumSize = new System.Drawing.Size(420, 0); + this.resetLink.Name = "resetLink"; + this.resetLink.Size = new System.Drawing.Size(52, 20); + this.resetLink.StrictColors = false; + this.resetLink.TabIndex = 23; + this.resetLink.TabStop = true; + this.resetLink.Text = "Reset"; + this.resetLink.ThemedBack = null; + this.resetLink.ThemedFore = null; + this.resetLink.VisitedLinkColor = System.Drawing.Color.MediumOrchid; + this.resetLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.ResetSelections); + // + // selectAllLink + // + this.selectAllLink.ActiveLinkColor = System.Drawing.Color.DarkOrchid; + this.selectAllLink.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.selectAllLink.AutoSize = true; + this.selectAllLink.Cursor = System.Windows.Forms.Cursors.Hand; + this.selectAllLink.HoverColor = System.Drawing.Color.MediumOrchid; + this.selectAllLink.LinkColor = System.Drawing.SystemColors.ControlDark; + this.selectAllLink.Location = new System.Drawing.Point(408, 16); + this.selectAllLink.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.selectAllLink.MaximumSize = new System.Drawing.Size(420, 0); + this.selectAllLink.Name = "selectAllLink"; + this.selectAllLink.Size = new System.Drawing.Size(75, 20); + this.selectAllLink.StrictColors = false; + this.selectAllLink.TabIndex = 19; + this.selectAllLink.TabStop = true; + this.selectAllLink.Text = "Select All"; + this.selectAllLink.ThemedBack = null; + this.selectAllLink.ThemedFore = null; + this.selectAllLink.VisitedLinkColor = System.Drawing.Color.MediumOrchid; + this.selectAllLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.SelectAllNotebooks); + // + // sep1 + // + this.sep1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.sep1.AutoSize = true; + this.sep1.Location = new System.Drawing.Point(490, 16); + this.sep1.Name = "sep1"; + this.sep1.Size = new System.Drawing.Size(14, 20); + this.sep1.TabIndex = 22; + this.sep1.Text = "|"; + this.sep1.ThemedBack = null; + this.sep1.ThemedFore = null; + // + // selectNoneLink + // + this.selectNoneLink.ActiveLinkColor = System.Drawing.Color.DarkOrchid; + this.selectNoneLink.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.selectNoneLink.AutoSize = true; + this.selectNoneLink.Cursor = System.Windows.Forms.Cursors.Hand; + this.selectNoneLink.HoverColor = System.Drawing.Color.MediumOrchid; + this.selectNoneLink.LinkColor = System.Drawing.SystemColors.ControlDark; + this.selectNoneLink.Location = new System.Drawing.Point(511, 16); + this.selectNoneLink.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.selectNoneLink.MaximumSize = new System.Drawing.Size(420, 0); + this.selectNoneLink.Name = "selectNoneLink"; + this.selectNoneLink.Size = new System.Drawing.Size(96, 20); + this.selectNoneLink.StrictColors = false; + this.selectNoneLink.TabIndex = 20; + this.selectNoneLink.TabStop = true; + this.selectNoneLink.Text = "Select None"; + this.selectNoneLink.ThemedBack = null; + this.selectNoneLink.ThemedFore = null; + this.selectNoneLink.VisitedLinkColor = System.Drawing.Color.MediumOrchid; + this.selectNoneLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.SelectNoneNotebooks); + // // buttonPanel // this.buttonPanel.BackColor = System.Drawing.SystemColors.Control; @@ -136,9 +289,9 @@ private void InitializeComponent() this.buttonPanel.Controls.Add(this.okButton); this.buttonPanel.Dock = System.Windows.Forms.DockStyle.Bottom; this.buttonPanel.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonPanel.Location = new System.Drawing.Point(0, 377); + this.buttonPanel.Location = new System.Drawing.Point(0, 648); this.buttonPanel.Name = "buttonPanel"; - this.buttonPanel.Size = new System.Drawing.Size(698, 61); + this.buttonPanel.Size = new System.Drawing.Size(718, 61); this.buttonPanel.TabIndex = 5; // // cancelButton @@ -148,7 +301,7 @@ private void InitializeComponent() this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.cancelButton.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); this.cancelButton.ImageOver = null; - this.cancelButton.Location = new System.Drawing.Point(571, 13); + this.cancelButton.Location = new System.Drawing.Point(591, 13); this.cancelButton.Name = "cancelButton"; this.cancelButton.ShowBorder = true; this.cancelButton.Size = new System.Drawing.Size(115, 36); @@ -166,7 +319,7 @@ private void InitializeComponent() this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; this.okButton.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); this.okButton.ImageOver = null; - this.okButton.Location = new System.Drawing.Point(450, 13); + this.okButton.Location = new System.Drawing.Point(470, 13); this.okButton.Name = "okButton"; this.okButton.ShowBorder = true; this.okButton.Size = new System.Drawing.Size(115, 36); @@ -182,8 +335,8 @@ private void InitializeComponent() this.introBox.Dock = System.Windows.Forms.DockStyle.Top; this.introBox.Location = new System.Drawing.Point(0, 0); this.introBox.Name = "introBox"; - this.introBox.Padding = new System.Windows.Forms.Padding(30, 12, 20, 12); - this.introBox.Size = new System.Drawing.Size(698, 62); + this.introBox.Padding = new System.Windows.Forms.Padding(30, 20, 20, 12); + this.introBox.Size = new System.Drawing.Size(718, 83); this.introBox.TabIndex = 11; this.introBox.Text = "The hashtag catalog has not yet been created. Choose below when OneMore should bu" + "ild the catalog."; @@ -196,7 +349,8 @@ private void InitializeComponent() this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.cancelButton; - this.ClientSize = new System.Drawing.Size(698, 438); + this.ClientSize = new System.Drawing.Size(718, 709); + this.Controls.Add(this.notebooksPanel); this.Controls.Add(this.topPanel); this.Controls.Add(this.introBox); this.Controls.Add(this.buttonPanel); @@ -204,11 +358,14 @@ private void InitializeComponent() this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MaximizeBox = false; this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(740, 760); this.Name = "ScheduleScanDialog"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Schedule Hashtag Scanning"; this.topPanel.ResumeLayout(false); this.topPanel.PerformLayout(); + this.notebooksPanel.ResumeLayout(false); + this.notebooksPanel.PerformLayout(); this.buttonPanel.ResumeLayout(false); this.ResumeLayout(false); @@ -227,5 +384,13 @@ private void InitializeComponent() private UI.MoreRadioButton nowRadio; private UI.MoreMultilineLabel introBox; private UI.MoreMultilineLabel warningLabel; + private UI.MoreLinkLabel selectNoneLink; + private UI.MoreLinkLabel selectAllLink; + private River.OneMoreAddIn.UI.MoreLabel notebooksLabel; + private UI.MorePanel booksPanel; + private UI.MoreLabel sep2; + private UI.MoreLinkLabel resetLink; + private UI.MoreLabel sep1; + private UI.MorePanel notebooksPanel; } } \ No newline at end of file diff --git a/OneMore/Commands/Tagging/ScheduleScanDialog.cs b/OneMore/Commands/Tagging/ScheduleScanDialog.cs index c956c6a3f2..52ada93dc7 100644 --- a/OneMore/Commands/Tagging/ScheduleScanDialog.cs +++ b/OneMore/Commands/Tagging/ScheduleScanDialog.cs @@ -6,12 +6,67 @@ namespace River.OneMoreAddIn.Commands { using River.OneMoreAddIn.UI; using System; + using System.Collections.Generic; + using System.Linq; + using System.Windows.Forms; using Resx = Properties.Resources; + /// + /// Presents to the user options to schedule a scan of selected notebooks. + /// internal partial class ScheduleScanDialog : MoreForm { - public ScheduleScanDialog() + #region Private classes + private sealed class NotebooksPanel : MoreFlowLayoutPanel + { + public NotebooksPanel() + { + FlowDirection = FlowDirection.LeftToRight; + AutoScroll = true; + WrapContents = true; + } + + + public event EventHandler SelectionsChanged; + + public void AddNotebook(string name, string notebookID, bool isChecked) + { + var box = new MoreCheckBox + { + Checked = isChecked, + Padding = new Padding(4, 2, 10, 2), + Tag = notebookID, + Text = name, + Width = Width - SystemInformation.VerticalScrollBarWidth + }; + + box.CheckedChanged += DoChangeSelections; + + Controls.Add(box); + } + + private void DoChangeSelections(object sender, EventArgs e) + { + SelectionsChanged?.Invoke(sender, e); + } + } + #endregion Private classes + + + private readonly NotebooksPanel booksFlow; + private List scannedIDs; + private List preferredIDs; + + + /// + /// Create a new schedule, starting midnight tonight, optionally allowing the user + /// to select notebooks to scan. + /// + /// + /// True to show the notebooks selection panel; false to hide it. + /// + public ScheduleScanDialog(bool showNotebooks) { InitializeComponent(); @@ -27,17 +82,50 @@ public ScheduleScanDialog() "scheduleLabel=word_Schedule", "nowRadio", "warningLabel", + "notebooksLabel=word_Notebooks", + "selectAllLink=phrase_SelectAll", + "selectNoneLink=phrase_SelectNone", + "resetLink=word_Reset", "okButton=word_OK", "cancelButton=word_Cancel" }); } dateTimePicker.Value = DateTime.Today.AddDays(1); + + if (showNotebooks) + { + booksFlow = new NotebooksPanel + { + Dock = DockStyle.Fill + }; + + booksFlow.SelectionsChanged += DoSelectionsChanged; + booksPanel.Controls.Add(booksFlow); + okButton.Enabled = false; + } + else + { + var height = Height - notebooksPanel.Height; + MinimumSize = new System.Drawing.Size(MinimumSize.Width, height); + MaximumSize = MinimumSize; + Height = height; + notebooksPanel.Visible = false; + } } - public ScheduleScanDialog(DateTime time) - : this() + /// + /// Use this constructor when modifying an existing schedule with a preset start time. + /// + /// + /// True to show the notebooks selection panel; false to hide it. + /// + /// + /// The start time of the existing schedule to be modified. + /// + public ScheduleScanDialog(bool showNotebooks, DateTime time) + : this(showNotebooks) { dateTimePicker.Value = time; } @@ -48,9 +136,153 @@ public ScheduleScanDialog(DateTime time) // DateTimePicker returns dates with Unspecified Kind, so force Local : DateTime.SpecifyKind(dateTimePicker.Value, DateTimeKind.Local); + + #region Load Helpers + private void AlignHyperlinks() + { + var resetSize = TextRenderer.MeasureText(resetLink.Text, resetLink.Font); + resetLink.Left = notebooksPanel.Width - notebooksPanel.Padding.Right - resetSize.Width; + + var sep2Size = TextRenderer.MeasureText(sep2.Text, sep2.Font); + sep2.Left = resetLink.Left - resetLink.Margin.Left - sep2Size.Width - sep2.Margin.Right; + + var noneSize = TextRenderer.MeasureText(selectNoneLink.Text, selectNoneLink.Font); + selectNoneLink.Left = sep2.Left - sep2.Margin.Left - noneSize.Width - selectNoneLink.Margin.Right; + + var sep1Size = TextRenderer.MeasureText(sep1.Text, sep1.Font); + sep1.Left = selectNoneLink.Left - selectNoneLink.Margin.Left - sep1Size.Width - sep1.Margin.Right; + + var allSize = TextRenderer.MeasureText(selectAllLink.Text, selectAllLink.Font); + selectAllLink.Left = sep1.Left - sep1.Margin.Left - allSize.Width - selectAllLink.Margin.Right; + } + + protected override async void OnLoad(EventArgs e) + { + if (booksFlow is not null) + { + if (HashtagProvider.DatabaseExists()) + { + using var provider = new HashtagProvider(); + scannedIDs = provider.ReadTaggedNotebookIDs(); + } + + await using var one = new OneNote(); + var books = await one.GetNotebooks(); + var ns = one.GetNamespace(books); + var anyChecked = false; + + foreach (var book in books.Elements(ns + "Notebook")) + { + var id = book.Attribute("ID").Value; + + var isChecked = + (preferredIDs is not null && preferredIDs.Contains(id)) || + scannedIDs is null || !scannedIDs.Contains(id); + + booksFlow.AddNotebook( + book.Attribute("name").Value, id, isChecked); + + if (isChecked) + { + anyChecked = true; + } + } + + if (anyChecked) + { + okButton.Enabled = true; + okButton.Focus(); + } + else + { + okButton.Enabled = false; + cancelButton.Focus(); + } + } + + base.OnLoad(e); + + AlignHyperlinks(); + } + #endregion Load Helpers + + + /// + /// Gets the list of selected notebook IDs + /// + /// An array of notebook IDs + public string[] GetSelectedNotebooks() + { + return booksFlow is null + ? new string[0] + : booksFlow.Controls.Cast() + .Where(c => c.Checked).Select(c => c.Tag.ToString()).ToArray(); + } + + + /// + /// Override the default intro text with customized text. + /// + /// public void SetIntroText(string text) { introBox.Text = text; } + + + /// + /// Sets the list of preferred (selected) notebook IDs. These are chosen by the user + /// as targets for a full [re]scan. They can be either known or new notebooks. + /// + /// + public void SetPreferredIDs(string[] preferredIDs) + { + this.preferredIDs = preferredIDs.ToList(); + } + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Handlers... + + private void DoSelectionsChanged(object sender, EventArgs e) + { + okButton.Enabled = booksFlow.Controls.Cast().Any(b => b.Checked); + + if (okButton.Enabled) + { + okButton.Focus(); + } + else + { + cancelButton.Focus(); + } + } + + + private void ResetSelections(object sender, LinkLabelLinkClickedEventArgs e) + { + foreach (MoreCheckBox box in booksFlow.Controls) + { + box.Checked = !scannedIDs.Contains((string)box.Tag); + } + } + + + private void SelectAllNotebooks(object sender, LinkLabelLinkClickedEventArgs e) + { + foreach (MoreCheckBox box in booksFlow.Controls) + { + box.Checked = true; + } + } + + + private void SelectNoneNotebooks(object sender, LinkLabelLinkClickedEventArgs e) + { + foreach (MoreCheckBox box in booksFlow.Controls) + { + box.Checked = false; + } + } } } diff --git a/OneMore/Helpers/Logger.cs b/OneMore/Helpers/Logger.cs index 1286994410..697e2146a9 100644 --- a/OneMore/Helpers/Logger.cs +++ b/OneMore/Helpers/Logger.cs @@ -426,7 +426,7 @@ private string MakeHeader() { if (!stdio) { - var bar = isVerbose ? "{" : "|"; + var bar = isVerbose ? ">" : "|"; return $"{Thread.CurrentThread.ManagedThreadId:00}|{DateTime.Now:hh:mm:ss.fff}{bar} {preamble}"; } diff --git a/OneMore/Models/MetaNames.cs b/OneMore/Models/MetaNames.cs index c346f6c64e..9c04e03de8 100644 --- a/OneMore/Models/MetaNames.cs +++ b/OneMore/Models/MetaNames.cs @@ -36,6 +36,7 @@ internal static class MetaNames public static readonly string TaggingBank = "omTaggingBank"; // page tag list + // TODO: temporary page tagging public static readonly string TaggingLabels = "omTaggingLabels"; // word count report for section or notebook diff --git a/OneMore/OneMore.csproj b/OneMore/OneMore.csproj index f32bd871fd..3ce653f1f1 100644 --- a/OneMore/OneMore.csproj +++ b/OneMore/OneMore.csproj @@ -250,6 +250,7 @@ HashtagContextControl.cs + Form diff --git a/OneMore/Properties/Resources.Designer.cs b/OneMore/Properties/Resources.Designer.cs index cffcb8185a..50378533c3 100644 --- a/OneMore/Properties/Resources.Designer.cs +++ b/OneMore/Properties/Resources.Designer.cs @@ -3470,6 +3470,15 @@ internal static string HashtagDialog_scanButton_Text { } } + /// + /// Looks up a localized string similar to Schedule Scan. + /// + internal static string HashtagDialog_scheduleButton_Text { + get { + return ResourceManager.GetString("HashtagDialog_scheduleButton.Text", resourceCulture); + } + } + /// /// Looks up a localized string similar to All ///This notebook @@ -5718,6 +5727,24 @@ internal static string phrase_QuickNote { } } + /// + /// Looks up a localized string similar to Select All. + /// + internal static string phrase_SelectAll { + get { + return ResourceManager.GetString("phrase_SelectAll", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select None. + /// + internal static string phrase_SelectNone { + get { + return ResourceManager.GetString("phrase_SelectNone", resourceCulture); + } + } + /// /// Looks up a localized string similar to PlantUML text is either invalid or cannot find connection between text and image. /// @@ -9487,6 +9514,15 @@ internal static string ribSaveSnippetButton_Screentip { } } + /// + /// Looks up a localized string similar to Schedule Hashtag Scan. + /// + internal static string ribScheduleHashtagScanButton_Label { + get { + return ResourceManager.GetString("ribScheduleHashtagScanButton_Label", resourceCulture); + } + } + /// /// Looks up a localized string similar to Search and Replace. /// @@ -12583,6 +12619,15 @@ internal static string word_Notebook { } } + /// + /// Looks up a localized string similar to Notebooks. + /// + internal static string word_Notebooks { + get { + return ResourceManager.GetString("word_Notebooks", resourceCulture); + } + } + /// /// Looks up a localized string similar to Notes. /// diff --git a/OneMore/Properties/Resources.ar-SA.resx b/OneMore/Properties/Resources.ar-SA.resx index 183c87e769..d67d0e1938 100644 --- a/OneMore/Properties/Resources.ar-SA.resx +++ b/OneMore/Properties/Resources.ar-SA.resx @@ -1306,6 +1306,10 @@ إفحص الآن menu button + + جدولة المسح + menu item + الجميع هذا دفتر @@ -2097,6 +2101,12 @@ ملاحظة سريعة + + اختر الكل + + + لا تختر شيء + نص PlantUML إما غير صالح أو لا يمكنه العثور على اتصال بين النص والصورة error message @@ -3763,6 +3773,10 @@ ISO-code then comma then language name احفظ مقتطفًا مخصصًا جديدًا من المحتوى المحدد ribbon snippets + + جدولة مسح الهاشتاج + palette command + البحث والاستبدال Ribbon OneMore menu item, search and replace @@ -5053,6 +5067,9 @@ ISO-code then comma then language name دفتر + + دفاتر الملاحظات + ملحوظات diff --git a/OneMore/Properties/Resources.de-DE.resx b/OneMore/Properties/Resources.de-DE.resx index 2225948b4c..d27ceb0dd3 100644 --- a/OneMore/Properties/Resources.de-DE.resx +++ b/OneMore/Properties/Resources.de-DE.resx @@ -1304,6 +1304,10 @@ Die Suche nach Hashtags ist erst möglich, wenn dies abgeschlossen ist. Scanne jetzt menu button + + Planen Sie den Scan + menu item + Alle Dieses Notizbuch @@ -2095,6 +2099,12 @@ Bewerben Sie sich auf dieses Notizbuch Schnelle Notiz + + Wählen Sie Alle + + + Nichts ausgewählt + PlantUML-Text ist entweder ungültig oder kann keine Verbindung zwischen Text und Bild finden error message @@ -3755,6 +3765,10 @@ Nach der letzten Gruppe Speichert ein neues benutzerdefiniertes Snippet aus dem ausgewählten Inhalt ribbon snippets + + Planen Sie den Hashtag-Scan + palette command + Suchen und Ersetzen Ribbon OneMore menu item, search and replace @@ -5045,6 +5059,9 @@ Das Dialogfeld „Hashtags suchen“ wird nun geöffnet. Notizbuch + + Notizbücher + Anmerkungen diff --git a/OneMore/Properties/Resources.es-ES.resx b/OneMore/Properties/Resources.es-ES.resx index 08afecf55c..a995bbadd6 100644 --- a/OneMore/Properties/Resources.es-ES.resx +++ b/OneMore/Properties/Resources.es-ES.resx @@ -1306,6 +1306,10 @@ La búsqueda de hashtags no estará disponible hasta que se complete. Escanear ahora menu button + + Programar escaneo + menu item + Todo este cuaderno @@ -2097,6 +2101,12 @@ Aplicar a este cuaderno Nota rápida + + Seleccionar todo + + + Seleccionar Ninguno + El texto de PlantUML no es válido o no puede encontrar una conexión entre el texto y la imagen error message @@ -3763,6 +3773,10 @@ Después del último grupo Guardar un nuevo fragmento personalizado del contenido seleccionado ribbon snippets + + Programar escaneo de hashtags + palette command + Buscar y reemplazar Ribbon OneMore menu item, search and replace @@ -5053,6 +5067,9 @@ Ahora se abrirá el cuadro de diálogo Buscar hashtags. Computadora portátil + + Cuadernos + notas diff --git a/OneMore/Properties/Resources.fr-FR.resx b/OneMore/Properties/Resources.fr-FR.resx index d0996e5755..f79d8eef5b 100644 --- a/OneMore/Properties/Resources.fr-FR.resx +++ b/OneMore/Properties/Resources.fr-FR.resx @@ -1306,6 +1306,10 @@ La recherche de hashtags n'est pas disponible tant qu'elle n'est pas terminée.< Scanne maintenant menu button + + Planifier une analyse + menu item + Tous Ce carnet @@ -2097,6 +2101,12 @@ Postuler à ce carnet Note rapide + + Tout sélectionner + + + Ne rien sélectionner + Le texte PlantUML est invalide ou ne peut pas trouver de connexion entre le texte et l'image error message @@ -3761,6 +3771,10 @@ Après le dernier groupe Enregistrer un nouvel extrait personnalisé à partir du contenu sélectionné ribbon snippets + + Planifier une analyse de hashtag + palette command + Rechercher et remplacer Ribbon OneMore menu item, search and replace @@ -5050,6 +5064,9 @@ La boîte de dialogue Rechercher des hashtags va maintenant s'ouvrir. Carnet de notes + + Des cahiers + Remarques diff --git a/OneMore/Properties/Resources.he-IL.resx b/OneMore/Properties/Resources.he-IL.resx index 2b632bf9df..2239542559 100644 --- a/OneMore/Properties/Resources.he-IL.resx +++ b/OneMore/Properties/Resources.he-IL.resx @@ -1315,6 +1315,10 @@ Total Row Font לסרוק עכשיו menu button + + תזמון סריקה + menu item + את כל המחברת הזו @@ -2106,6 +2110,12 @@ Total Row Font הערה מהירה + + בחר הכל + + + אל תבחר באף אחת מהאפשרויות + טקסט PlantUML אינו חוקי או שאינו יכול למצוא קשר בין טקסט לתמונה error message @@ -3774,6 +3784,10 @@ ISO-code then comma then language name שמור קטע מותאם אישית חדש מהתוכן שנבחר ribbon snippets + + תזמן סריקת Hashtag + palette command + חפש והחלף Ribbon OneMore menu item, search and replace NODUP @@ -5068,6 +5082,9 @@ ISO-code then comma then language name מחברת + + מחברות + הערות diff --git a/OneMore/Properties/Resources.nl-NL.resx b/OneMore/Properties/Resources.nl-NL.resx index f153939a57..ce1b17aef0 100644 --- a/OneMore/Properties/Resources.nl-NL.resx +++ b/OneMore/Properties/Resources.nl-NL.resx @@ -1307,6 +1307,10 @@ Zoeken naar hashtags is pas beschikbaar als dit is voltooid. Scan nu menu button + + Plan scannen + menu item + Alle Dit notitieboekje @@ -2098,6 +2102,12 @@ Toepassen op dit notitieboekje Snelle notitie + + Selecteer alles + + + Selecteer Geen + PlantUML-tekst is ongeldig of kan geen verband vinden tussen tekst en afbeelding error message @@ -3763,6 +3773,10 @@ Na de laatste groep Sla een nieuw aangepast fragment op van de geselecteerde inhoud ribbon snippets + + Plan een hashtagscan + palette command + Zoeken en vervangen Ribbon OneMore menu item, search and replace @@ -5053,6 +5067,9 @@ Het dialoogvenster Hashtags zoeken wordt nu geopend. Notitieboekje + + Notitieboekjes + Notities diff --git a/OneMore/Properties/Resources.pl-PL.resx b/OneMore/Properties/Resources.pl-PL.resx index 289d694eb7..91f0df3243 100644 --- a/OneMore/Properties/Resources.pl-PL.resx +++ b/OneMore/Properties/Resources.pl-PL.resx @@ -1315,6 +1315,10 @@ Wyszukiwanie hashtagów nie jest możliwe, dopóki nie zostanie zakończone.Skanuj teraz menu button + + Zaplanuj skanowanie + menu item + Wszystko Ten notatnik @@ -2106,6 +2110,12 @@ Zastosuj do tego notatnika Krótka notatka + + Zaznacz wszystko + + + Wybierz opcję Brak + Tekst PlantUML jest nieprawidłowy lub nie może znaleźć połączenia między tekstem a obrazem error message @@ -3776,6 +3786,10 @@ Po ostatniej grupie Zapisz nowy niestandardowy fragment z wybranej treści ribbon snippets + + Zaplanuj skanowanie hashtagów + palette command + Wyszukaj i zamień Ribbon OneMore menu item, search and replace @@ -5070,6 +5084,9 @@ Otworzy się teraz okno dialogowe Znajdź hashtagi. Zeszyt + + Notatniki + Notatki diff --git a/OneMore/Properties/Resources.pt-BR.resx b/OneMore/Properties/Resources.pt-BR.resx index 86ad21dfa6..302b035f61 100644 --- a/OneMore/Properties/Resources.pt-BR.resx +++ b/OneMore/Properties/Resources.pt-BR.resx @@ -1307,6 +1307,10 @@ A pesquisa de hashtags não estará disponível até que isso seja concluído.Escaneie agora menu button + + Agendar verificação + menu item + Todos Este caderno @@ -2098,6 +2102,12 @@ Aplicar a este caderno Nota rápida + + Selecionar tudo + + + Selecione nenhum + O texto PlantUML é inválido ou não consegue encontrar a conexão entre o texto e a imagem error message @@ -3763,6 +3773,10 @@ Depois do último grupo Salve um novo snippet personalizado do conteúdo selecionado ribbon snippets + + Agendar verificação de hashtag + palette command + Pesquisar e substituir Ribbon OneMore menu item, search and replace @@ -5053,6 +5067,9 @@ A caixa de diálogo Encontrar hashtags será aberta agora. Caderno + + Cadernos + Notas diff --git a/OneMore/Properties/Resources.resx b/OneMore/Properties/Resources.resx index 287eb9372c..829e56661c 100644 --- a/OneMore/Properties/Resources.resx +++ b/OneMore/Properties/Resources.resx @@ -1319,6 +1319,10 @@ Searching for hashtags is unavailable until this is completed. Scan Now menu button + + Schedule Scan + menu item + All This notebook @@ -2122,6 +2126,12 @@ Apply to this notebook Quick Note + + Select All + + + Select None + PlantUML text is either invalid or cannot find connection between text and image error message @@ -3796,6 +3806,10 @@ After last group Save a new snippet from the selected content ribbon snippets + + Schedule Hashtag Scan + palette command + Search and Replace ribbon search NODUP @@ -5095,6 +5109,9 @@ The Find Hashtags dialog will now open. Notebook + + Notebooks + Notes diff --git a/OneMore/Properties/Resources.zh-CN.resx b/OneMore/Properties/Resources.zh-CN.resx index a72e12226b..b863fc01ed 100644 --- a/OneMore/Properties/Resources.zh-CN.resx +++ b/OneMore/Properties/Resources.zh-CN.resx @@ -1305,6 +1305,10 @@ OneNote 文件 (*.one) 现在扫描 menu button + + 安排扫描 + menu item + 全部 这个笔记本 @@ -2096,6 +2100,12 @@ OneNote 文件 (*.one) 快速注释 + + 全选 + + + 选择无 + PlantUML 文本无效或找不到文本和图像之间的连接 error message @@ -3756,6 +3766,10 @@ ISO-code then comma then language name 从所选内容中保存新的自定义片段 ribbon snippets + + 安排标签扫描 + palette command + 搜索和替换 Ribbon OneMore menu item, search and replace @@ -5046,6 +5060,9 @@ ISO-code then comma then language name 笔记本 + + 笔记本电脑 + 笔记 diff --git a/OneMore/UI/MoreListView.cs b/OneMore/UI/MoreListView.cs index 9baea8318b..3bf2a7dbe8 100644 --- a/OneMore/UI/MoreListView.cs +++ b/OneMore/UI/MoreListView.cs @@ -227,6 +227,12 @@ public MoreHostedListViewItem AddHostedItem(string text, Control control = null) private void RegisterHostedControl(IMoreHostItem subitem, RegistrationEventArgs e) { + if (InvokeRequired) + { + Invoke(new Action(() => { RegisterHostedControl(subitem, e); })); + return; + } + hostedControls.Add(new HostedControl(subitem, e.Item, e.Control, e.ColumnIndex)); Controls.Add(e.Control); } diff --git a/OneMoreTray/App.config b/OneMoreTray/App.config index aee9adf485..4881d0a85b 100644 --- a/OneMoreTray/App.config +++ b/OneMoreTray/App.config @@ -1,6 +1,9 @@  - - - + + + + + + \ No newline at end of file diff --git a/OneMoreTray/ScanningJob.cs b/OneMoreTray/ScanningJob.cs index a7031d3f4e..606af11a63 100644 --- a/OneMoreTray/ScanningJob.cs +++ b/OneMoreTray/ScanningJob.cs @@ -110,6 +110,12 @@ private void Execute() trayIcon.ShowBalloonTip(0, Resx.ScannerTitle, Resx.ScanStarting, ToolTipIcon.Info); var scanner = new HashtagService(scheduler.State == ScanningState.PendingRebuild); + + if (scheduler.Notebooks is not null && scheduler.Notebooks.Length > 0) + { + scanner.SetNotebookFilters(scheduler.Notebooks); + } + scanner.OnHashtagScanned += DoScanned; scanner.Startup(); @@ -154,7 +160,8 @@ private void ToastMissingSchedule() private void DoReschedule(object sender, EventArgs e) { - using var dialog = new ScheduleScanDialog(scheduler.StartTime); + var showBooks = scheduler.Notebooks.Length > 0; + using var dialog = new ScheduleScanDialog(showBooks, scheduler.StartTime); var msg = string.Format( scheduler.State == ScanningState.PendingScan @@ -163,11 +170,15 @@ private void DoReschedule(object sender, EventArgs e) scheduler.StartTime.ToString(Resx.ScheduleTimeFormat)); dialog.SetIntroText(msg); + dialog.SetPreferredIDs(scheduler.Notebooks); + dialog.StartPosition = FormStartPosition.CenterScreen; var result = dialog.ShowDialog(); if (result == DialogResult.OK) { source.Cancel(false); + + scheduler.Notebooks = dialog.GetSelectedNotebooks(); scheduler.StartTime = dialog.StartTime; scheduler.SaveSchedule(); ScheduleScan();