Crunchy-Downloader/CRD/ViewModels/UpdateViewModel.cs
Elwador aca28a4e17 Add - Added option to **mux fonts into the MKV**
Add - Added **completion sound** when downloads finish, allowing a specified sound to be played
Add - Added **changelog** for easier tracking of changes and updates
Add - Added **Retry button** to reset all failed downloads, allowing "Auto Download" to restart them
Add - Added **dub/sub info** to the Upcoming tab
Chg - Changed **history table view** to include missing features from the poster view
Chg - Changed and **adjusted error messages**
Chg - Changed **chapters formatting** for consistent output across locales
Chg - Changed **Clear Queue** button to icon-only
Chg - Changed **update button** behavior
Chg - Changed some **error messages** for better debugging
Chg - Changed **history series access**, now allowing it to open while others are refreshing
Chg - Changed **device ID reuse** to fix continuous login emails
Chg - Changed **authentication log** to write the full message for better debugging
Chg - Updated **dependencies**
Fix - Temporary fix for **authentication issues**
Fix - Fixed **unable to download movies** [#237](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/237)
Fix - Fixed **buggy queue behavior** during active downloads
Fix - Fixed **duplicate seasons in history** when adding multiple episodes from the calendar
Fix - Fixed crash if  **all** subtitles option is selected
Fix - Fixed "**Cannot set download directory to Drive**" https://github.com/Crunchy-DL/Crunchy-Downloader/issues/220
Fix - Fixed missing **subtitles and none subs** for some series
2025-04-04 20:12:29 +02:00

188 lines
No EOL
5.9 KiB
C#

using System;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using Avalonia;
using Avalonia.Media;
using Avalonia.Styling;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CRD.Downloader;
using CRD.Utils.Updater;
using Markdig;
namespace CRD.ViewModels;
public partial class UpdateViewModel : ViewModelBase{
[ObservableProperty]
private bool _updateAvailable;
[ObservableProperty]
private bool _updating;
[ObservableProperty]
private double _progress;
[ObservableProperty]
private bool _failed;
private AccountPageViewModel accountPageViewModel;
[ObservableProperty]
private string _changelogText = "<p><strong>No changelog found.</strong></p>";
[ObservableProperty]
private string _currentVersion;
public UpdateViewModel(){
var version = Assembly.GetExecutingAssembly().GetName().Version;
_currentVersion = $"{version?.Major}.{version?.Minor}.{version?.Build}";
LoadChangelog();
UpdateAvailable = ProgramManager.Instance.UpdateAvailable;
Updater.Instance.PropertyChanged += Progress_PropertyChanged;
}
[RelayCommand]
public void StartUpdate(){
Updating = true;
ProgramManager.Instance.NavigationLock = true;
// Title = "Updating";
_ = Updater.Instance.DownloadAndUpdateAsync();
}
private void Progress_PropertyChanged(object? sender, PropertyChangedEventArgs e){
if (e.PropertyName == nameof(Updater.Instance.progress)){
Progress = Updater.Instance.progress;
} else if (e.PropertyName == nameof(Updater.Instance.failed)){
Failed = Updater.Instance.failed;
ProgramManager.Instance.NavigationLock = !Failed;
}
}
private void LoadChangelog(){
string changelogPath = "CHANGELOG.md";
if (!File.Exists(changelogPath)){
ChangelogText = "<p><strong>No changelog found.</strong></p>";
return;
}
string markdownText = File.ReadAllText(changelogPath);
markdownText = PreprocessMarkdown(markdownText);
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.UseSoftlineBreakAsHardlineBreak()
.Build();
string htmlContent = Markdown.ToHtml(markdownText, pipeline);
htmlContent = MakeIssueLinksClickable(htmlContent);
htmlContent = ModifyImages(htmlContent);
Color themeTextColor = Application.Current?.RequestedThemeVariant == ThemeVariant.Dark ? Colors.White : Color.Parse("#E4000000");
string cssColor = $"#{themeTextColor.R:X2}{themeTextColor.G:X2}{themeTextColor.B:X2}";
string styledHtml = $@"
<html>
<head>
<style type=""text/css"">
body {{
color: {cssColor};
background: transparent;
font-family: Arial, sans-serif;
}}
img {{
max-width: 100%;
height: auto;
display: block;
margin: 10px auto;
max-height: 300px;
object-fit: contain;
}}
li {{
margin-bottom: 10px;
line-height: 1.6;
}}
code {{
background: #f0f0f0;
font-family: Consolas, Monaco, 'Courier New', monospace;
white-space: nowrap;
vertical-align: middle;
display: inline-block;
}}
pre code {{
background: #f5f5f5;
display: block;
padding: 10px;
border-radius: 5px;
white-space: pre-wrap;
word-wrap: break-word;
font-family: Consolas, Monaco, 'Courier New', monospace;
}}
</style>
</head>
<body>
{htmlContent}
</body>
</html>";
ChangelogText = styledHtml;
}
private string MakeIssueLinksClickable(string htmlContent){
// Match GitHub issue links
string issuePattern = @"<a href=['""](https:\/\/github\.com\/Crunchy-DL\/Crunchy-Downloader\/issues\/(\d+))['""][^>]*>[^<]+<\/a>";
// Match GitHub discussion links
string discussionPattern = @"<a href=['""](https:\/\/github\.com\/Crunchy-DL\/Crunchy-Downloader\/discussions\/(\d+))['""][^>]*>[^<]+<\/a>";
htmlContent = Regex.Replace(htmlContent, issuePattern, match => {
string fullUrl = match.Groups[1].Value;
string issueNumber = match.Groups[2].Value;
return $"<a href='{fullUrl}' target='_blank'>#{issueNumber}</a>";
});
htmlContent = Regex.Replace(htmlContent, discussionPattern, match => {
string fullUrl = match.Groups[1].Value;
string discussionNumber = match.Groups[2].Value;
return $"<a href='{fullUrl}' target='_blank'>#{discussionNumber}</a>";
});
return htmlContent;
}
private string ModifyImages(string htmlContent){
// Regex to match <img> tags
string imgPattern = @"<img\s+src=['""]([^'""]+)['""]( alt=['""]([^'""]+)['""])?\s*\/?>";
return Regex.Replace(htmlContent, imgPattern, match => {
string imgUrl = match.Groups[1].Value;
string altText = "View Image"; // match.Groups[3].Success ? match.Groups[3].Value : "View Image";
return $"<a href='{imgUrl}' target='_blank'>{altText}</a>";
});
}
private string PreprocessMarkdown(string markdownText){
// Regex to match <details> blocks containing an image
string detailsPattern = @"<details>\s*<summary>.*?<\/summary>\s*<img\s+src=['""]([^'""]+)['""]\s+alt=['""]([^'""]+)['""]\s*\/?>\s*<\/details>";
return Regex.Replace(markdownText, detailsPattern, match => {
string imageUrl = match.Groups[1].Value;
string altText = match.Groups[2].Value;
return $"![{altText}]({imageUrl})";
});
}
}