Merge pull request #1 from Dumpy250/feature/cache

DRAFT: Feature/cache
This commit is contained in:
Stone_Red
2024-05-16 19:39:18 +02:00
committed by GitHub
11 changed files with 307 additions and 26 deletions
+140
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
Markdowser
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RiderProjectSettingsUpdater">
<option name="vcsConfiguration" value="2" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
+80
View File
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoGeneratedRunConfigurationManager">
<projectFile>Markdowser/Markdowser.csproj</projectFile>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="35ad98ab-fce0-4c9e-9180-ec9795d8ed8e" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="DpaMonitoringSettings">
<option name="autoShow" value="false" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 6
}]]></component>
<component name="ProjectId" id="2fg8GifhQMdOrTygPnNJLDd0rxo" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
".NET Project.Markdowser.executor": "Run",
"git-widget-placeholder": "develop",
"node.js.selected.package.tslint": "(autodetect)"
},
"keyToStringList": {
"rider.external.source.directories": [
"C:\\Users\\Camer\\AppData\\Roaming\\JetBrains\\Rider2024.1\\resharper-host\\DecompilerCache",
"C:\\Users\\Camer\\AppData\\Roaming\\JetBrains\\Rider2024.1\\resharper-host\\SourcesCache",
"C:\\Users\\Camer\\AppData\\Local\\Symbols\\src"
]
}
}]]></component>
<component name="RunManager">
<configuration name="Markdowser" type="DotNetProject" factoryName=".NET Project" temporary="true">
<option name="EXE_PATH" value="$PROJECT_DIR$/Markdowser/bin/Debug/net8.0/Markdowser.exe" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/Markdowser/bin/Debug/net8.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Markdowser/Markdowser.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net8.0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue=".NET Project.Markdowser" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="UnityProjectConfiguration" hasMinimizedUI="false" />
<component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
</component>
</project>
+27
View File
@@ -0,0 +1,27 @@
using Markdowser.ViewModels;
using System.Collections.Generic;
namespace Markdowser.Utilities;
public class CacheService
{
private readonly Dictionary<string, ContentViewModelBase> cache = [];
public ContentViewModelBase? Get(string url)
{
if (cache.TryGetValue(url, out ContentViewModelBase? value))
{
return value;
}
else
{
return null;
}
}
public void Set(string url, ContentViewModelBase content)
{
cache[url] = content;
}
}
@@ -25,6 +25,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!;
@@ -48,7 +49,7 @@ public partial class MainWindowViewModel : ViewModelBase
Url = value?.Tag?.ToString() ?? string.Empty;
FetchUrl();
FetchUrl(true);
}
}
@@ -99,7 +100,8 @@ public partial class MainWindowViewModel : ViewModelBase
public bool BackEnabled => GlobalState.BackHistory.Count > 0;
public bool ForwardEnabled => GlobalState.ForwardHistory.Count > 0;
public bool ProgressIndeterminate => Progress == 0;
public ICommand Browse => ReactiveCommand.Create(FetchUrl);
public ICommand Browse => ReactiveCommand.Create(() => FetchUrl(true));
public ICommand Reload => ReactiveCommand.Create(() => FetchUrl(false));
public ICommand Back => ReactiveCommand.Create(() =>
{
@@ -208,7 +210,7 @@ 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(bool useCache = true)
{
if (IsBusy)
{
@@ -260,32 +262,41 @@ public partial class MainWindowViewModel : ViewModelBase
try
{
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url);
string? newUrl = httpResponseMessage.RequestMessage?.RequestUri?.ToString();
if (newUrl is not null && newUrl != Url)
ContentViewModelBase? cachedContent = cacheService.Get(Url);
if (cachedContent is not null && useCache)
{
string oldUrl = Url;
Dispatcher.UIThread.Post(() => WindowNotificationManager.Show(new Notification("Redirected", $"Redirected from\n{oldUrl}\nto\n{newUrl}", NotificationType.Warning)));
Url = newUrl;
Content = cachedContent;
}
if (!httpResponseMessage.IsSuccessStatusCode)
else
{
IsBusy = false;
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url);
StringBuilder errorMessage = new();
string? newUrl = httpResponseMessage.RequestMessage?.RequestUri?.ToString();
_ = errorMessage.AppendLine($"# {(int)httpResponseMessage.StatusCode} {httpResponseMessage.StatusCode}");
_ = errorMessage.AppendLine($"Failed to fetch URL: {httpResponseMessage.ReasonPhrase}");
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;
}
Content = new MarkdownContentViewModel("Error", errorMessage.ToString());
return;
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<ProcessingProgress>(p => Progress = p.Percentage));
cacheService.Set(Url, Content);
}
Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress<ProcessingProgress>(p => Progress = p.Percentage));
Dispatcher.UIThread.Post(() => CurrentTab.Header = Content.Title);
Logger.LogInfo($"Fetched URL: {Url}");
@@ -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">
<Grid>
@@ -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">
<UserControl.Resources>
+3 -3
View File
@@ -31,8 +31,8 @@
</Window.Resources>
<Window.KeyBindings>
<KeyBinding Gesture="Ctrl+R" Command="{Binding Browse}" />
<KeyBinding Gesture="F5" Command="{Binding Browse}" />
<KeyBinding Gesture="Ctrl+R" Command="{Binding Reload}" />
<KeyBinding Gesture="F5" Command="{Binding Reload}" />
<KeyBinding Gesture="Alt+Home" Command="{StaticResource HomeCommand}" />
<KeyBinding Gesture="Alt+H" Command="{StaticResource HomeCommand}" />
<KeyBinding Gesture="Alt+Left" Command="{Binding Back}" />
@@ -104,7 +104,7 @@
</MultiBinding>
</Button.IsEnabled>
</Button>
<Button Grid.Column="2" i:Attached.Icon="fa-solid fa-rotate-right" Command="{Binding Browse}" IsEnabled="{Binding !IsBusy}" />
<Button Grid.Column="2" i:Attached.Icon="fa-solid fa-rotate-right" Command="{Binding Reload}" IsEnabled="{Binding !IsBusy}" />
<Button Grid.Column="3" i:Attached.Icon="fa-solid fa-house" Command="{StaticResource HomeCommand}" IsEnabled="{Binding !IsBusy}" />
<Grid Grid.Column="4">