Upgrade to .NET 9, code cleanup.
This commit is contained in:
parent
7573492bcf
commit
ee5b5f8dc3
13 changed files with 62 additions and 115 deletions
|
|
@ -51,12 +51,12 @@ public class TorrentData(DataContext dataContext)
|
|||
|
||||
public async Task<Torrent?> GetByHash(String hash)
|
||||
{
|
||||
#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
|
||||
hash = hash.ToLower();
|
||||
|
||||
var dbTorrent = await dataContext.Torrents
|
||||
.AsNoTracking()
|
||||
.Include(m => m.Downloads)
|
||||
.FirstOrDefaultAsync(m => m.Hash.ToLower() == hash.ToLower());
|
||||
#pragma warning restore CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
|
||||
.FirstOrDefaultAsync(m => m.Hash == hash);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
using System;
|
||||
using System.Web;
|
||||
using RdtClient.Data.Enums;
|
||||
using System.Web;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Service.Services;
|
||||
|
||||
namespace RdtClient.Service.Helpers;
|
||||
|
||||
public static class DownloadHelper
|
||||
{
|
||||
public static async Task<String?> GetDownloadPath(String downloadPath, Torrent torrent, Download download)
|
||||
public static String? GetDownloadPath(String downloadPath, Torrent torrent, Download download)
|
||||
{
|
||||
var fileUrl = download.Link;
|
||||
|
||||
|
|
@ -24,20 +21,6 @@ public static class DownloadHelper
|
|||
|
||||
var fileName = uri.Segments.Last();
|
||||
|
||||
if (Settings.Get.Provider.Provider == Provider.TorBox)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Head, uri);
|
||||
HttpResponseMessage response = await client.SendAsync(request);
|
||||
|
||||
if (response.Content.Headers.ContentDisposition != null)
|
||||
{
|
||||
fileName = response.Content.Headers.ContentDisposition.FileName!.Trim('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileName = HttpUtility.UrlDecode(fileName);
|
||||
|
||||
fileName = FileHelper.RemoveInvalidFileNameChars(fileName);
|
||||
|
|
@ -68,7 +51,7 @@ public static class DownloadHelper
|
|||
return filePath;
|
||||
}
|
||||
|
||||
public static async Task<String?> GetDownloadPath(Torrent torrent, Download download)
|
||||
public static String? GetDownloadPath(Torrent torrent, Download download)
|
||||
{
|
||||
var fileUrl = download.Link;
|
||||
|
||||
|
|
@ -82,20 +65,6 @@ public static class DownloadHelper
|
|||
|
||||
var fileName = uri.Segments.Last();
|
||||
|
||||
if (Settings.Get.Provider.Provider == Provider.TorBox)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Head, uri);
|
||||
HttpResponseMessage response = await client.SendAsync(request);
|
||||
|
||||
if (response.Content.Headers.ContentDisposition != null)
|
||||
{
|
||||
fileName = response.Content.Headers.ContentDisposition.FileName!.Trim('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileName = HttpUtility.UrlDecode(fileName);
|
||||
|
||||
fileName = FileHelper.RemoveInvalidFileNameChars(fileName);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
|
@ -11,15 +11,15 @@
|
|||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="AllDebrid.NET" Version="1.0.12" />
|
||||
<PackageReference Include="Aria2.NET" Version="1.0.5" />
|
||||
<PackageReference Include="Downloader" Version="3.1.2" />
|
||||
<PackageReference Include="Downloader" Version="3.2.1" />
|
||||
<PackageReference Include="Downloader.NET" Version="1.0.12" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
|
||||
<PackageReference Include="MonoTorrent" Version="3.0.2" />
|
||||
<PackageReference Include="Polly" Version="8.4.1" />
|
||||
<PackageReference Include="Polly" Version="8.5.0" />
|
||||
<PackageReference Include="Premiumize.NET" Version="1.0.4" />
|
||||
<PackageReference Include="RD.NET" Version="2.1.6" />
|
||||
<PackageReference Include="Serilog" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.38.0" />
|
||||
<PackageReference Include="TorBox.NET" Version="1.2.1" />
|
||||
|
|
|
|||
|
|
@ -43,15 +43,15 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
|
|||
|
||||
if (Type != Data.Enums.DownloadClient.Symlink)
|
||||
{
|
||||
await FileHelper.Delete(filePath.Result!);
|
||||
await FileHelper.Delete(filePath);
|
||||
}
|
||||
|
||||
Downloader = Type switch
|
||||
{
|
||||
Data.Enums.DownloadClient.Internal => new InternalDownloader(download.Link, filePath.Result!),
|
||||
Data.Enums.DownloadClient.Bezzad => new BezzadDownloader(download.Link, filePath.Result!),
|
||||
Data.Enums.DownloadClient.Aria2c => new Aria2cDownloader(download.RemoteId, download.Link, filePath.Result!, downloadPath.Result!, category),
|
||||
Data.Enums.DownloadClient.Symlink => new SymlinkDownloader(download.Link, filePath.Result!, downloadPath.Result!),
|
||||
Data.Enums.DownloadClient.Internal => new InternalDownloader(download.Link, filePath),
|
||||
Data.Enums.DownloadClient.Bezzad => new BezzadDownloader(download.Link, filePath),
|
||||
Data.Enums.DownloadClient.Aria2c => new Aria2cDownloader(download.RemoteId, download.Link, filePath, downloadPath, category),
|
||||
Data.Enums.DownloadClient.Symlink => new SymlinkDownloader(download.Link, filePath, downloadPath),
|
||||
_ => throw new($"Unknown download client {Type}")
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ public class InternalDownloader : IDownloader
|
|||
{
|
||||
settingDownloadMaxSpeed = 0;
|
||||
}
|
||||
settingDownloadMaxSpeed = settingDownloadMaxSpeed / Math.Max(TorrentRunner.ActiveDownloadClients.Count, 1);
|
||||
settingDownloadMaxSpeed /= Math.Max(TorrentRunner.ActiveDownloadClients.Count, 1);
|
||||
settingDownloadMaxSpeed = settingDownloadMaxSpeed * 1024 * 1024;
|
||||
|
||||
var settingDownloadTimeout = Settings.Get.DownloadClient.Timeout;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using Newtonsoft.Json;
|
|||
using TorBoxNET;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
using RdtClient.Service.Helpers;
|
||||
|
||||
namespace RdtClient.Service.Services.TorrentClients;
|
||||
|
||||
|
|
@ -74,9 +73,9 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
|||
Progress = (Int64)((torrent.Progress) * 100.0),
|
||||
Status = torrent.DownloadState,
|
||||
Added = ChangeTimeZone(torrent.CreatedAt)!.Value,
|
||||
Files = (torrent.Files ?? []).Select(m => new TorrentClientFile
|
||||
Files = torrent.Files.Select(m => new TorrentClientFile
|
||||
{
|
||||
Path = string.Join("/", m.Name.Split('/').Skip(1)),
|
||||
Path = String.Join("/", m.Name.Split('/').Skip(1)),
|
||||
Bytes = m.Size,
|
||||
Id = m.Id,
|
||||
Selected = true
|
||||
|
|
@ -104,7 +103,7 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
|||
torrents.AddRange(queuedTorrents);
|
||||
}
|
||||
|
||||
return torrents!.Select(Map).ToList();
|
||||
return torrents.Select(Map).ToList();
|
||||
}
|
||||
|
||||
public async Task<TorrentClientUser> GetUser()
|
||||
|
|
@ -141,26 +140,20 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
|||
|
||||
public async Task<String> AddFile(Byte[] bytes)
|
||||
{
|
||||
var seeding = Settings.Get.Integrations.Default.FinishedAction;
|
||||
|
||||
// Line is not working right now, will disable seeding and fix in december when I have time again.
|
||||
// var seed = (seeding == TorrentFinishedAction.RemoveAllTorrents || seeding == TorrentFinishedAction.RemoveRealDebrid) ? 3 : 2;
|
||||
const Int32 seed = 3;
|
||||
|
||||
var seed = 3;
|
||||
|
||||
var result = await GetClient().Torrents.AddFileAsync(bytes, seed, false);
|
||||
var result = await GetClient().Torrents.AddFileAsync(bytes, seed);
|
||||
if (result.Error == "ACTIVE_LIMIT")
|
||||
{
|
||||
using (var stream = new MemoryStream(bytes))
|
||||
{
|
||||
var torrent = MonoTorrent.Torrent.Load(stream);
|
||||
return torrent!.InfoHashes.V1!.ToHex().ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return result.Data!.Hash!;
|
||||
using var stream = new MemoryStream(bytes);
|
||||
|
||||
var torrent = await MonoTorrent.Torrent.LoadAsync(stream);
|
||||
return torrent.InfoHashes.V1!.ToHex().ToLowerInvariant();
|
||||
}
|
||||
|
||||
return result.Data!.Hash!;
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
|
|
@ -169,24 +162,15 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
|||
|
||||
if (availability.Data != null)
|
||||
{
|
||||
var availableFiles = new List<TorrentClientAvailableFile>();
|
||||
foreach (var file in availability.Data[0]?.Files!)
|
||||
{
|
||||
availableFiles.Add(
|
||||
new TorrentClientAvailableFile
|
||||
{
|
||||
Filename = file!.Name,
|
||||
Filesize = file.Size
|
||||
});
|
||||
}
|
||||
return availableFiles;
|
||||
}
|
||||
else
|
||||
{
|
||||
return [];
|
||||
return (availability.Data[0]?.Files ?? []).Select(file => new TorrentClientAvailableFile()
|
||||
{
|
||||
Filename = file.Name,
|
||||
Filesize = file.Size
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public Task SelectFiles(Data.Models.Data.Torrent torrent)
|
||||
|
|
@ -199,21 +183,21 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
|||
await GetClient().Torrents.ControlAsync(torrentId, "delete");
|
||||
}
|
||||
|
||||
public async Task<string> Unrestrict(string link)
|
||||
public async Task<String> Unrestrict(String link)
|
||||
{
|
||||
var segments = link.Split('/');
|
||||
|
||||
bool zipped = segments[5] == "zip";
|
||||
string fileId = zipped ? "0" : segments[5];
|
||||
var zipped = segments[5] == "zip";
|
||||
var fileId = zipped ? "0" : segments[5];
|
||||
|
||||
var result = await GetClient().Torrents.RequestDownloadAsync(Convert.ToInt32(segments[4]), Convert.ToInt32(fileId), zipped);
|
||||
|
||||
if (result?.Error != null)
|
||||
if (result.Error != null)
|
||||
{
|
||||
throw new("Unrestrict returned an invalid download");
|
||||
}
|
||||
|
||||
return result!.Data!;
|
||||
return result.Data!;
|
||||
}
|
||||
|
||||
public async Task<Data.Models.Data.Torrent> UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent? torrentClientTorrent)
|
||||
|
|
@ -303,12 +287,6 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
|||
|
||||
public async Task<IList<String>?> GetDownloadLinks(Data.Models.Data.Torrent torrent)
|
||||
{
|
||||
if (torrent.Hash == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var rdTorrent = await GetInfo(torrent.Hash);
|
||||
var files = new List<String>();
|
||||
|
||||
var torrentId = await GetClient().Torrents.GetHashInfoAsync(torrent.Hash, skipCache: true);
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@ public class Torrents(
|
|||
{
|
||||
Log($"Deleting {filePath}", download, download.Torrent);
|
||||
|
||||
await FileHelper.Delete(filePath.Result!);
|
||||
await FileHelper.Delete(filePath);
|
||||
}
|
||||
|
||||
Log($"Resetting", download, download.Torrent);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class UnpackClient(Download download, String destinationPath)
|
|||
{
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
await Unpack(filePath.Result!, _cancellationTokenSource.Token);
|
||||
await Unpack(filePath, _cancellationTokenSource.Token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.QBittorrent;
|
||||
using RdtClient.Service.Services;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
|
|
@ -136,8 +135,6 @@ builder.Host.UseWindowsService();
|
|||
RdtClient.Data.DiConfig.Config(builder.Services, appSettings);
|
||||
builder.Services.RegisterRdtServices();
|
||||
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||
|
||||
try
|
||||
{
|
||||
// Build the app
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
|
||||
<Version>2.0.86</Version>
|
||||
|
|
@ -29,17 +29,17 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="8.0.8" />
|
||||
<PackageReference Include="Serilog" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -199,10 +199,12 @@
|
|||
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EPsiFeatures_002EVisualStudio_002EBackend_002EDaemon_002EHighlightingSettingsMigrator/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Housekeeping/VsHighlighting/SuppressVsSquiggles/@EntryValue">True</s:Boolean>
|
||||
</wpf:ResourceDictionary>
|
||||
Loading…
Reference in a new issue