From 6f797b81744d0e31381e5653877d2cf07e5ae633 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 27 Apr 2024 18:09:19 -0400 Subject: [PATCH 1/5] implement cache attempt --- src/.idea/.idea.Markdowser/.idea/.name | 1 + .../.idea.Markdowser/.idea/indexLayout.xml | 8 + .../.idea/projectSettingsUpdater.xml | 6 + src/.idea/.idea.Markdowser/.idea/vcs.xml | 6 + .../.idea.Markdowser/.idea/workspace.xml | 80 ++++++++++ src/Markdowser/Utilities/CacheService.cs | 30 ++++ .../ViewModels/MainWindowViewModel.cs | 149 ++++++++++-------- .../Content/CommonImageContentView.axaml | 3 +- .../Views/Content/MarkdownContentView.axaml | 3 +- 9 files changed, 215 insertions(+), 71 deletions(-) create mode 100644 src/.idea/.idea.Markdowser/.idea/.name create mode 100644 src/.idea/.idea.Markdowser/.idea/indexLayout.xml create mode 100644 src/.idea/.idea.Markdowser/.idea/projectSettingsUpdater.xml create mode 100644 src/.idea/.idea.Markdowser/.idea/vcs.xml create mode 100644 src/.idea/.idea.Markdowser/.idea/workspace.xml create mode 100644 src/Markdowser/Utilities/CacheService.cs diff --git a/src/.idea/.idea.Markdowser/.idea/.name b/src/.idea/.idea.Markdowser/.idea/.name new file mode 100644 index 0000000..3442041 --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/.name @@ -0,0 +1 @@ +Markdowser \ No newline at end of file diff --git a/src/.idea/.idea.Markdowser/.idea/indexLayout.xml b/src/.idea/.idea.Markdowser/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Markdowser/.idea/projectSettingsUpdater.xml b/src/.idea/.idea.Markdowser/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000..4bb9f4d --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Markdowser/.idea/vcs.xml b/src/.idea/.idea.Markdowser/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Markdowser/.idea/workspace.xml b/src/.idea/.idea.Markdowser/.idea/workspace.xml new file mode 100644 index 0000000..760e254 --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/workspace.xml @@ -0,0 +1,80 @@ + + + + Markdowser/Markdowser.csproj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Markdowser/Utilities/CacheService.cs b/src/Markdowser/Utilities/CacheService.cs new file mode 100644 index 0000000..2cff353 --- /dev/null +++ b/src/Markdowser/Utilities/CacheService.cs @@ -0,0 +1,30 @@ +using Markdowser.ViewModels; +using System.Collections.Generic; + +namespace Markdowser.Utilities +{ + namespace Markdowser.Utilities + { + public class CacheService + { + private Dictionary cache = new Dictionary(); + + public ContentViewModelBase Get(string url) + { + if (cache.ContainsKey(url)) + { + return cache[url]; + } + else + { + return null; + } + } + + public void Set(string url, ContentViewModelBase content) + { + cache[url] = content; + } + } + } +} \ No newline at end of file diff --git a/src/Markdowser/ViewModels/MainWindowViewModel.cs b/src/Markdowser/ViewModels/MainWindowViewModel.cs index 44fcc0c..3d68d7c 100644 --- a/src/Markdowser/ViewModels/MainWindowViewModel.cs +++ b/src/Markdowser/ViewModels/MainWindowViewModel.cs @@ -6,6 +6,7 @@ using Markdowser.Models; using Markdowser.Processing; using Markdowser.Processing.Processors; using Markdowser.Utilities; +using Markdowser.Utilities.Markdowser.Utilities; using Markdowser.ViewModels.Content; using ReactiveUI; @@ -26,6 +27,7 @@ public partial class MainWindowViewModel : ViewModelBase { private readonly HttpClient httpClient = new(new HttpClientHandler() { AllowAutoRedirect = true }); private readonly ContentProcessorManager contentProcessorManager = new(); + private readonly CacheService cacheService = new CacheService(); private ContentViewModelBase content; private TabItem currentTab = null!; @@ -196,50 +198,57 @@ public partial class MainWindowViewModel : ViewModelBase return Uri.TryCreate(uriString, UriKind.Absolute, out uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps); } - private void FetchUrl() +private void FetchUrl() +{ + if (IsBusy) { - if (IsBusy) - { - WindowNotificationManager.Show(new Notification("Busy", "The browser is currently busy.", NotificationType.Warning)); - return; - } + WindowNotificationManager.Show(new Notification("Busy", "The browser is currently busy.", NotificationType.Warning)); + return; + } - if (string.IsNullOrWhiteSpace(Url)) - { - Content = DefaultContent; - Debug.WriteLine("URL is empty."); - CurrentTab.Header = "New Tab"; - return; - } + if (string.IsNullOrWhiteSpace(Url)) + { + Content = DefaultContent; + Debug.WriteLine("URL is empty."); + CurrentTab.Header = "New Tab"; + return; + } - if (!IsValidHttpUri(Url, out _)) + if (!IsValidHttpUri(Url, out _)) + { + if (Url.StartsWith("//")) { - if (Url.StartsWith("//")) + Url = $"https:{Url}"; + } + else + { + // Search with duckduckgo + try { - Url = $"https:{Url}"; + Url = string.Format(Settings.Current.SearchEngineUrl, Uri.EscapeDataString(Url)); + } + catch (FormatException ex) + { + WindowNotificationManager.Show(new Notification("Invalid Search Engine URL", $"{ex.Message}", NotificationType.Error)); + } + } + } + + IsBusy = true; + Progress = 0; + + _ = Task.Run(async () => + { + Debug.WriteLine("Fetching URL..."); + + try + { + ContentViewModelBase cachedContent = cacheService.Get(Url); + if (cachedContent != null) + { + Content = cachedContent; } else - { - // Search with duckduckgo - try - { - Url = string.Format(Settings.Current.SearchEngineUrl, Uri.EscapeDataString(Url)); - } - catch (FormatException ex) - { - WindowNotificationManager.Show(new Notification("Invalid Search Engine URL", $"{ex.Message}", NotificationType.Error)); - } - } - } - - IsBusy = true; - Progress = 0; - - _ = Task.Run(async () => - { - Debug.WriteLine("Fetching URL..."); - - try { HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url); @@ -266,42 +275,44 @@ public partial class MainWindowViewModel : ViewModelBase } Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress(p => Progress = p.Percentage)); - - Dispatcher.UIThread.Post(() => CurrentTab.Header = Content.Title); + cacheService.Set(Url, Content); } - catch (HttpRequestException e) + + Dispatcher.UIThread.Post(() => CurrentTab.Header = Content.Title); + } + catch (HttpRequestException e) + { + StringBuilder errorMessage = new(); + + if (e.StatusCode is not null) { - StringBuilder errorMessage = new(); - - if (e.StatusCode is not null) - { - _ = errorMessage.AppendLine($"# {(int)e.StatusCode} {e.StatusCode}"); - Dispatcher.UIThread.Post(() => CurrentTab.Header = $"Error: {(int)e.StatusCode} {e.StatusCode}"); - } - else - { - Dispatcher.UIThread.Post(() => CurrentTab.Header = $"Error: {e.GetType().Name}"); - } - - _ = errorMessage.AppendLine($"# {e.HttpRequestError}"); - _ = errorMessage.AppendLine($"Failed to fetch URL: {e.Message}"); - - Content = new MarkdownContentViewModel("Error", errorMessage.ToString()); + _ = errorMessage.AppendLine($"# {(int)e.StatusCode} {e.StatusCode}"); + Dispatcher.UIThread.Post(() => CurrentTab.Header = $"Error: {(int)e.StatusCode} {e.StatusCode}"); } - catch (Exception e) + else { - StringBuilder errorMessage = new(); - - _ = errorMessage.AppendLine($"# {e.GetType().Name}"); - _ = errorMessage.AppendLine($"Failed to fetch URL: {e.Message}"); - - Content = new MarkdownContentViewModel("Error", errorMessage.ToString()); Dispatcher.UIThread.Post(() => CurrentTab.Header = $"Error: {e.GetType().Name}"); } - finally - { - IsBusy = false; - } - }); - } + + _ = errorMessage.AppendLine($"# {e.HttpRequestError}"); + _ = errorMessage.AppendLine($"Failed to fetch URL: {e.Message}"); + + Content = new MarkdownContentViewModel("Error", errorMessage.ToString()); + } + catch (Exception e) + { + StringBuilder errorMessage = new(); + + _ = errorMessage.AppendLine($"# {e.GetType().Name}"); + _ = errorMessage.AppendLine($"Failed to fetch URL: {e.Message}"); + + Content = new MarkdownContentViewModel("Error", errorMessage.ToString()); + Dispatcher.UIThread.Post(() => CurrentTab.Header = $"Error: {e.GetType().Name}"); + } + finally + { + IsBusy = false; + } + }); +} } \ No newline at end of file diff --git a/src/Markdowser/Views/Content/CommonImageContentView.axaml b/src/Markdowser/Views/Content/CommonImageContentView.axaml index 1426a1d..34c13f8 100644 --- a/src/Markdowser/Views/Content/CommonImageContentView.axaml +++ b/src/Markdowser/Views/Content/CommonImageContentView.axaml @@ -8,7 +8,8 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:md="https://github.com/whistyun/Markdown.Avalonia" xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia" - x:DataType="vm:Content.CommonImageContentViewModel" + xmlns:content="clr-namespace:Markdowser.ViewModels.Content" + x:DataType="content:CommonImageContentViewModel" x:Class="Markdowser.Views.Content.CommonImageContentView"> diff --git a/src/Markdowser/Views/Content/MarkdownContentView.axaml b/src/Markdowser/Views/Content/MarkdownContentView.axaml index 7ff5259..2627761 100644 --- a/src/Markdowser/Views/Content/MarkdownContentView.axaml +++ b/src/Markdowser/Views/Content/MarkdownContentView.axaml @@ -8,7 +8,8 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:md="https://github.com/whistyun/Markdown.Avalonia" xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia" - x:DataType="vm:Content.MarkdownContentViewModel" + xmlns:content="clr-namespace:Markdowser.ViewModels.Content" + x:DataType="content:MarkdownContentViewModel" x:Class="Markdowser.Views.Content.MarkdownContentView"> From 8ea493f2c82babc2a02fec0b22e29a14ed34fcb0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 29 Apr 2024 06:30:44 -0400 Subject: [PATCH 2/5] tweak .gitconfig --- .gitignore | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/.gitignore b/.gitignore index 426d76d..4f9edbb 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,143 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Rider ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# AWS User-specific + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr From 87d9e7abb6ad2c0d73b6fd72dceb50cd6cdc6899 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 29 Apr 2024 08:14:04 -0400 Subject: [PATCH 3/5] fixes/reload button fix --- src/Markdowser/Utilities/CacheService.cs | 7 +- .../ViewModels/MainWindowViewModel.cs | 92 ++++++++++++++++++- src/Markdowser/Views/MainWindow.axaml | 3 +- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/Markdowser/Utilities/CacheService.cs b/src/Markdowser/Utilities/CacheService.cs index 2cff353..2ed4999 100644 --- a/src/Markdowser/Utilities/CacheService.cs +++ b/src/Markdowser/Utilities/CacheService.cs @@ -1,15 +1,13 @@ using Markdowser.ViewModels; using System.Collections.Generic; -namespace Markdowser.Utilities -{ namespace Markdowser.Utilities { public class CacheService { private Dictionary cache = new Dictionary(); - public ContentViewModelBase Get(string url) + public ContentViewModelBase? Get(string url) { if (cache.ContainsKey(url)) { @@ -26,5 +24,4 @@ namespace Markdowser.Utilities cache[url] = content; } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/src/Markdowser/ViewModels/MainWindowViewModel.cs b/src/Markdowser/ViewModels/MainWindowViewModel.cs index 3d68d7c..dc37d03 100644 --- a/src/Markdowser/ViewModels/MainWindowViewModel.cs +++ b/src/Markdowser/ViewModels/MainWindowViewModel.cs @@ -6,7 +6,6 @@ using Markdowser.Models; using Markdowser.Processing; using Markdowser.Processing.Processors; using Markdowser.Utilities; -using Markdowser.Utilities.Markdowser.Utilities; using Markdowser.ViewModels.Content; using ReactiveUI; @@ -103,6 +102,8 @@ public partial class MainWindowViewModel : ViewModelBase public bool ForwardEnabled => GlobalState.ForwardHistory.Count > 0; public bool ProgressIndeterminate => Progress == 0; public ICommand Browse => ReactiveCommand.Create(FetchUrl); + public ICommand Reload => ReactiveCommand.Create(FetchUrlIgnoringCache); + public ICommand Back => ReactiveCommand.Create(() => { @@ -315,4 +316,93 @@ private void FetchUrl() } }); } + +private void FetchUrlIgnoringCache() +{ + if (IsBusy) + { + WindowNotificationManager.Show(new Notification("Busy", "The browser is currently busy.", NotificationType.Warning)); + return; + } + + if (string.IsNullOrWhiteSpace(Url)) + { + Content = DefaultContent; + Debug.WriteLine("URL is empty."); + CurrentTab.Header = "New Tab"; + return; + } + + if (!IsValidHttpUri(Url, out _)) + { + if (Url.StartsWith("//")) + { + Url = $"https:{Url}"; + } + else + { + // Search with duckduckgo + try + { + Url = string.Format(Settings.Current.SearchEngineUrl, Uri.EscapeDataString(Url)); + } + catch (FormatException ex) + { + WindowNotificationManager.Show(new Notification("Invalid Search Engine URL", $"{ex.Message}", NotificationType.Error)); + } + } + } + + IsBusy = true; + Progress = 0; + + _ = Task.Run(async () => + { + Debug.WriteLine("Fetching URL..."); + + try + { + HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url); + + string? newUrl = httpResponseMessage.RequestMessage?.RequestUri?.ToString(); + + if (newUrl is not null && newUrl != Url) + { + string oldUrl = Url; + Dispatcher.UIThread.Post(() => WindowNotificationManager.Show(new Notification("Redirected", $"Redirected from\n{oldUrl}\nto\n{newUrl}", NotificationType.Warning))); + Url = newUrl; + } + + if (!httpResponseMessage.IsSuccessStatusCode) + { + IsBusy = false; + + StringBuilder errorMessage = new(); + + _ = errorMessage.AppendLine($"# {(int)httpResponseMessage.StatusCode} {httpResponseMessage.StatusCode}"); + _ = errorMessage.AppendLine($"Failed to fetch URL: {httpResponseMessage.ReasonPhrase}"); + + Content = new MarkdownContentViewModel("Error", errorMessage.ToString()); + return; + } + + Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress(p => Progress = p.Percentage)); + cacheService.Set(Url, Content); + + Dispatcher.UIThread.Post(() => CurrentTab.Header = Content.Title); + } + catch (HttpRequestException e) + { + // Handle exception + } + catch (Exception e) + { + // Handle exception + } + finally + { + IsBusy = false; + } + }); +} } \ No newline at end of file diff --git a/src/Markdowser/Views/MainWindow.axaml b/src/Markdowser/Views/MainWindow.axaml index e634897..b7edad5 100644 --- a/src/Markdowser/Views/MainWindow.axaml +++ b/src/Markdowser/Views/MainWindow.axaml @@ -32,7 +32,7 @@ - + @@ -85,6 +85,7 @@