diff --git a/OneMore/Commands/References/CheckUrlsCommand.cs b/OneMore/Commands/References/CheckUrlsCommand.cs index a9597fb6b8..c50304b15e 100644 --- a/OneMore/Commands/References/CheckUrlsCommand.cs +++ b/OneMore/Commands/References/CheckUrlsCommand.cs @@ -6,6 +6,7 @@ namespace River.OneMoreAddIn.Commands { using River.OneMoreAddIn.Models; using River.OneMoreAddIn.Styles; + using River.OneMoreAddIn.UI; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -14,10 +15,19 @@ namespace River.OneMoreAddIn.Commands using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; + using Resx = Properties.Resources; internal class CheckUrlsCommand : Command { + private OneNote one; + private Page page; + private List candidates; + private Dictionary map; + + private int badCount; + private Exception exception; + public CheckUrlsCommand() { @@ -28,45 +38,21 @@ public override async Task Execute(params object[] args) { if (!HttpClientFactory.IsNetworkAvailable()) { - ShowInfo(Properties.Resources.NetwordConnectionUnavailable); + ShowInfo(Resx.NetwordConnectionUnavailable); return; } - await using var one = new OneNote(out var page, out _); - - var count = await HasInvalidUrls(page); - if (count > 0) - { - await one.Update(page); - - UI.MoreMessageBox.ShowWarning(owner, $"Found {count} invalid URLs on this page"); - } - } - - - private async Task HasInvalidUrls(Page page) - { - var elements = GetCandiateElements(page); - - // parallelize internet access for all chosen hyperlinks on the page... - - var count = 0; - if (elements.Any()) + await using (one = new OneNote(out page, out _)) { - // must use a thread-safe collection here - var tasks = new ConcurrentBag>(); - - foreach (var element in elements) + candidates = GetCandiateElements(page); + if (candidates.Count > 0) { - // do not use await in the body loop; just build list of tasks - tasks.Add(ValidateUrls(element)); - } + var progressDialog = new ProgressDialog(Execute); - await Task.WhenAll(tasks.ToArray()); - count = tasks.Sum(t => t.IsFaulted ? 0 : t.Result); + // report results on UI thread after execution + progressDialog.RunModeless(ReportResult); + } } - - return count; } @@ -105,16 +91,127 @@ private static List GetCandiateElements(Page page) } - private async Task ValidateUrls(XElement element) + // Invoked by the ProgressDialog OnShown callback + private async Task Execute(ProgressDialog progress, CancellationToken token) + { + logger.Start(); + logger.StartClock(); + + var scope = GetOneNoteScope(); + if (scope != OneNote.Scope.Self) + { + await BuildHyperlinkMap(scope, progress, token); + } + + progress.SetMaximum(candidates.Count); + progress.SetMessage(string.Format(Resx.CheckUrlsCommand_checkingMsg, candidates.Count)); + + try + { + await ValidateUrls(progress); + if (badCount > 0) + { + await one.Update(page); + } + } + catch (Exception exc) + { + logger.WriteLine("error validating URLs", exc); + exception = exc; + } + + progress.Close(); + + logger.WriteTime("check complete"); + logger.End(); + } + + + private OneNote.Scope GetOneNoteScope() + { + /* + * Notebook reference will start with "onenote:https://...." + * onenote:https://d.docs.live.net/6925.../&section-id={...}&page-id={...}&end + * + * Any pages within this notebook will have a base-path=https... + * onenote:...&section-id={...}&page-id={...}&end&base-path=https://d... + * + * Possible future optimization: collect all named notebooks/sections since the + * notebook URI contains the exact names, here "OneMore Wiki" and "Get Started" + * https://d.docs.live.net/.../Documents/OneMore%20Wiki/Get%20Started.one + */ + + var scope = OneNote.Scope.Self; + + foreach (var candidate in candidates) + { + var data = candidates.DescendantNodes().OfType(); + if (data.Any(d => d.Value.Contains(" d.Value.Contains(" + { + progress.SetMaximum(count); + progress.SetMessage(string.Format(Resx.CheckUrlsCommand_mappingMsg, count)); + await Task.Yield(); + }, + async () => + { + progress.Increment(); + await Task.Yield(); + }); + } + + + private async Task ValidateUrls(ProgressDialog progress) + { + // parallelize internet access for all chosen hyperlinks on the page... + + // must use a thread-safe collection here + var tasks = new ConcurrentBag(); + + foreach (var candidate in candidates) + { + // do not use await in the body loop; just build list of tasks + tasks.Add(ValidateUrl(candidate, progress)); + } + + await Task.WhenAll(tasks.ToArray()); + //var count = tasks.Sum(t => t.IsFaulted ? 0 : t.Result); + } + + + private async Task ValidateUrl(XElement element, ProgressDialog progress) { var cdata = element.GetCData(); var wrapper = cdata.GetWrapper(); - var count = 0; + var count = badCount; foreach (var anchor in wrapper.Elements("a")) { + progress.Increment(); + var href = anchor.Attribute("href")?.Value; - if (ValidWebAddress(href)) + if (ValidAddress(href)) { if (await InvalidUrl(href)) { @@ -144,34 +241,68 @@ private async Task ValidateUrls(XElement element) ); } - count++; + Interlocked.Increment(ref badCount); } } } - if (count > 0) + if (badCount > count) { cdata.ReplaceWith(wrapper.GetInnerXml()); } - - return count; } - private static bool ValidWebAddress(string href) + private static bool ValidAddress(string href) { - return - !string.IsNullOrWhiteSpace(href) && - href.StartsWith("http") && + if (string.IsNullOrWhiteSpace(href)) + { + return false; + } + + if (href.StartsWith("http") && !( - href.StartsWith("https://onedrive.live.com/view.aspx") && + href.Contains("onedrive.live.com/view.aspx") && href.Contains("&id=documents") && href.Contains(".one") - ); + )) + { + return true; + } + + return + href.StartsWith("onenote:") && + href.Contains("section-id=") && + href.Contains("page-id="); } private async Task InvalidUrl(string url) + { + if (url.StartsWith("onenote:") && url.Contains("page-id=")) + { + return InvalidOneNoteUrl(url); + } + else + { + return await InvalidWebUrl(url); + } + } + + + private bool InvalidOneNoteUrl(string url) + { + var match = Regex.Match(url, @"section-id=({[^}]+})&page-id=({[^}]+})"); + if (match.Success) + { + return !map.ContainsKey(match.Groups[2].Value); + } + + return false; + } + + + private async Task InvalidWebUrl(string url) { var invalid = false; @@ -212,5 +343,31 @@ private async Task InvalidUrl(string url) return invalid; } + + + private void ReportResult(object sender, EventArgs e) + { + // report results back on the main UI thread... + + if (sender is ProgressDialog progress) + { + // otherwise ShowMessage window will appear behind progress dialog + progress.Visible = false; + } + + if (exception is null) + { + if (badCount > 0) + { + MoreMessageBox.ShowWarning(owner, + string.Format(Resx.CheckUrlsCommand_invaldiMsg, badCount)); + } + } + else + { + MoreMessageBox.ShowErrorWithLogLink(owner, exception.Message); + } + } + } } diff --git a/OneMore/Properties/Resources.Designer.cs b/OneMore/Properties/Resources.Designer.cs index 6bdffbab91..4341be0bf7 100644 --- a/OneMore/Properties/Resources.Designer.cs +++ b/OneMore/Properties/Resources.Designer.cs @@ -900,6 +900,33 @@ internal static string Calculator_ErrUndefinedVariable { } } + /// + /// Looks up a localized string similar to Checking {0} URLs. + /// + internal static string CheckUrlsCommand_checkingMsg { + get { + return ResourceManager.GetString("CheckUrlsCommand_checkingMsg", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found {0} invalid URLs on this page. + /// + internal static string CheckUrlsCommand_invaldiMsg { + get { + return ResourceManager.GetString("CheckUrlsCommand_invaldiMsg", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mapping {0} page references. + /// + internal static string CheckUrlsCommand_mappingMsg { + get { + return ResourceManager.GetString("CheckUrlsCommand_mappingMsg", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cleaned up {0} orphaned reminders. /// diff --git a/OneMore/Properties/Resources.ar-SA.resx b/OneMore/Properties/Resources.ar-SA.resx index 53a7001e6d..104fa02353 100644 --- a/OneMore/Properties/Resources.ar-SA.resx +++ b/OneMore/Properties/Resources.ar-SA.resx @@ -485,6 +485,18 @@ متغير غير محدد "{0}" error + + التحقق من {0} عناوين URL + message + + + تم العثور على {0} عنوان URL غير صالح في هذه الصفحة + message + + + تعيين مراجع الصفحة {0}. + message + تم تنظيف {0} من التذكيرات المعزولة message box diff --git a/OneMore/Properties/Resources.de-DE.resx b/OneMore/Properties/Resources.de-DE.resx index c50a633344..47bd11e3b9 100644 --- a/OneMore/Properties/Resources.de-DE.resx +++ b/OneMore/Properties/Resources.de-DE.resx @@ -485,6 +485,18 @@ Umkehren Undefinierte Variable „{0}“ error + + {0} URLs werden überprüft + message + + + Auf dieser Seite wurden {0} ungültige URLs gefunden + message + + + Zuordnung von {0} Seitenverweisen + message + {0} verwaiste Erinnerungen wurden bereinigt message box diff --git a/OneMore/Properties/Resources.es-ES.resx b/OneMore/Properties/Resources.es-ES.resx index 26d106f05b..980eaff714 100644 --- a/OneMore/Properties/Resources.es-ES.resx +++ b/OneMore/Properties/Resources.es-ES.resx @@ -485,6 +485,18 @@ Invertir Variable no definida "{0}" error + + Comprobando {0} URL + message + + + Se encontraron {0} URL no válidas en esta página + message + + + Asignación de {0} referencias de página + message + Se limpiaron {0} recordatorios huérfanos message box diff --git a/OneMore/Properties/Resources.fr-FR.resx b/OneMore/Properties/Resources.fr-FR.resx index f135f1ddd6..2efda8ab73 100644 --- a/OneMore/Properties/Resources.fr-FR.resx +++ b/OneMore/Properties/Resources.fr-FR.resx @@ -485,6 +485,18 @@ Inverser Variable non définie "{0}" error + + Vérification de {0} URL + message + + + {0} URL non valides trouvées sur cette page + message + + + Mappage de {0} références de page + message + Nettoyage de {0} rappels orphelins message box diff --git a/OneMore/Properties/Resources.he-IL.resx b/OneMore/Properties/Resources.he-IL.resx index 3b9cbb95fb..e9759d7dae 100644 --- a/OneMore/Properties/Resources.he-IL.resx +++ b/OneMore/Properties/Resources.he-IL.resx @@ -486,6 +486,18 @@ משתנה לא מוגדר "{0}" error + + בודק {0} כתובות אתרים + message + + + נמצאו {0} כתובות אתרים לא חוקיות בדף זה + message + + + מיפוי של {0} הפניות לדפים + message + ניקה {0} תזכורות יתומות message box diff --git a/OneMore/Properties/Resources.ja-JP.resx b/OneMore/Properties/Resources.ja-JP.resx index ad0ce38187..543375e80b 100644 --- a/OneMore/Properties/Resources.ja-JP.resx +++ b/OneMore/Properties/Resources.ja-JP.resx @@ -486,6 +486,18 @@ 未定義の変数「{0}」 error + + {0} 個の URL をチェックしています + message + + + このページで無効な URL が {0} 個見つかりました + message + + + {0} ページ参照をマッピングしています + message + {0} 件の孤立したリマインダーを整理しました。 message box diff --git a/OneMore/Properties/Resources.nl-NL.resx b/OneMore/Properties/Resources.nl-NL.resx index 01798026c2..6b98ab927d 100644 --- a/OneMore/Properties/Resources.nl-NL.resx +++ b/OneMore/Properties/Resources.nl-NL.resx @@ -486,6 +486,18 @@ Omkeren Ongedefinieerde variabele "{0}" error + + {0} URL's controleren + message + + + Er zijn {0} ongeldige URL's gevonden op deze pagina + message + + + Het in kaart brengen van {0} paginareferenties + message + {0} zwevende herinneringen opgeschoond message box diff --git a/OneMore/Properties/Resources.pl-PL.resx b/OneMore/Properties/Resources.pl-PL.resx index a31ec68173..ec62b04ca8 100644 --- a/OneMore/Properties/Resources.pl-PL.resx +++ b/OneMore/Properties/Resources.pl-PL.resx @@ -486,6 +486,18 @@ Odwracać Niezdefiniowana zmienna „{0}” error + + Sprawdzanie adresów URL: {0} + message + + + Znaleziono {0} nieprawidłowych adresów URL na tej stronie + message + + + Mapowanie odwołań do stron {0} + message + Poprawiono {0} osieroconych przypomnień message box diff --git a/OneMore/Properties/Resources.pt-BR.resx b/OneMore/Properties/Resources.pt-BR.resx index 9e3ac3d9d5..e8d3f1e8d3 100644 --- a/OneMore/Properties/Resources.pt-BR.resx +++ b/OneMore/Properties/Resources.pt-BR.resx @@ -486,6 +486,18 @@ Invertido Variável indefinida "{0}" error + + Verificando {0} URLs + message + + + Foram encontrados {0} URLs inválidos nesta página + message + + + Mapeando {0} referências de página + message + Foram limpos {0} lembretes órfãos message box diff --git a/OneMore/Properties/Resources.resx b/OneMore/Properties/Resources.resx index 3844ad8671..48f35a1441 100644 --- a/OneMore/Properties/Resources.resx +++ b/OneMore/Properties/Resources.resx @@ -486,6 +486,18 @@ Invert Undefined variable "{0}" error + + Checking {0} URLs + message + + + Found {0} invalid URLs on this page + message + + + Mapping {0} page references + message + Cleaned up {0} orphaned reminders message box diff --git a/OneMore/Properties/Resources.zh-CN.resx b/OneMore/Properties/Resources.zh-CN.resx index f6fe56c1b6..43fc77c54f 100644 --- a/OneMore/Properties/Resources.zh-CN.resx +++ b/OneMore/Properties/Resources.zh-CN.resx @@ -484,6 +484,18 @@ 未定义的变量“{0}” error + + 检查 {0} 个网址 + message + + + 在此页上发现 {0} 个无效网址 + message + + + 映射 {0} 页面引用 + message + 清理了 {0} 个孤立的提醒 message box