Merge branch 'master' into pr-main-repo

This commit is contained in:
Roger Far 2025-02-12 20:24:54 -07:00 committed by GitHub
commit c005bda484
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 120 additions and 146 deletions

View file

@ -44,7 +44,7 @@ public class Torrent
[InverseProperty("Torrent")] [InverseProperty("Torrent")]
public IList<Download> Downloads { get; set; } = []; public IList<Download> Downloads { get; set; } = [];
public TorrentClientKind? ClientKind { get; set; } public Provider? ClientKind { get; set; }
public String? RdId { get; set; } public String? RdId { get; set; }
public String? RdName { get; set; } public String? RdName { get; set; }
public Int64? RdSize { get; set; } public Int64? RdSize { get; set; }
@ -93,13 +93,4 @@ public class Torrent
return DownloadManualFiles.Split(","); return DownloadManualFiles.Split(",");
} }
} }
public enum TorrentClientKind
{
AllDebrid,
Premiumize,
RealDebrid,
TorBox,
DebridLink
}
} }

View file

@ -6,4 +6,5 @@ public class TorrentClientFile
public String Path { get; set; } = default!; public String Path { get; set; } = default!;
public Int64 Bytes { get; set; } public Int64 Bytes { get; set; }
public Boolean Selected { get; set; } public Boolean Selected { get; set; }
public String? DownloadLink { get; set; }
} }

View file

@ -8,11 +8,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.1" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View file

@ -9,22 +9,22 @@
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="AllDebrid.NET" Version="1.0.12" /> <PackageReference Include="AllDebrid.NET" Version="1.0.16" />
<PackageReference Include="Aria2.NET" Version="1.0.5" /> <PackageReference Include="Aria2.NET" Version="1.0.5" />
<PackageReference Include="DebridLinkFr.NET" Version="1.0.3" /> <PackageReference Include="DebridLinkFr.NET" Version="1.0.3" />
<PackageReference Include="Downloader" Version="3.3.3" /> <PackageReference Include="Downloader" Version="3.3.3" />
<PackageReference Include="Downloader.NET" Version="1.0.12" /> <PackageReference Include="Downloader.NET" Version="1.0.13" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.1" /> <PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.1.0" /> <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.2.0" />
<PackageReference Include="MonoTorrent" Version="3.0.2" /> <PackageReference Include="MonoTorrent" Version="3.0.2" />
<PackageReference Include="Polly" Version="8.5.1" /> <PackageReference Include="Polly" Version="8.5.2" />
<PackageReference Include="Premiumize.NET" Version="1.0.4" /> <PackageReference Include="Premiumize.NET" Version="1.0.4" />
<PackageReference Include="RD.NET" Version="2.1.6" /> <PackageReference Include="RD.NET" Version="2.1.6" />
<PackageReference Include="Serilog" Version="4.2.0" /> <PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="SharpCompress" Version="0.39.0" /> <PackageReference Include="SharpCompress" Version="0.39.0" />
<PackageReference Include="Synology.Api.Client" Version="0.3.87" /> <PackageReference Include="Synology.Api.Client" Version="0.3.87" />
<PackageReference Include="TorBox.NET" Version="1.3.2" /> <PackageReference Include="TorBox.NET" Version="1.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,4 +1,5 @@
using RdtClient.Data.Models.Data; using RdtClient.Data.Enums;
using RdtClient.Data.Models.Data;
using RdtClient.Service.Helpers; using RdtClient.Service.Helpers;
using RdtClient.Service.Services.Downloaders; using RdtClient.Service.Services.Downloaders;
using RdtClient.Service.Services.TorrentClients; using RdtClient.Service.Services.TorrentClients;
@ -40,7 +41,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
var filePath = DownloadHelper.GetDownloadPath(destinationPath, torrent, download); var filePath = DownloadHelper.GetDownloadPath(destinationPath, torrent, download);
var downloadPath = DownloadHelper.GetDownloadPath(torrent, download); var downloadPath = DownloadHelper.GetDownloadPath(torrent, download);
if (torrent.ClientKind == Torrent.TorrentClientKind.AllDebrid && Type == Data.Enums.DownloadClient.Symlink) if (torrent.ClientKind == Provider.AllDebrid && Type == Data.Enums.DownloadClient.Symlink)
{ {
downloadPath = AllDebridTorrentClient.GetSymlinkPath(torrent, download); downloadPath = AllDebridTorrentClient.GetSymlinkPath(torrent, download);
} }

View file

@ -1,10 +1,10 @@
using RdtClient.Data.Models.Data; using RdtClient.Data.Enums;
using RdtClient.Service.Helpers; using RdtClient.Service.Helpers;
using Serilog; using Serilog;
namespace RdtClient.Service.Services.Downloaders; namespace RdtClient.Service.Services.Downloaders;
public class SymlinkDownloader(String uri, String destinationPath, String path, Torrent.TorrentClientKind? clientKind) : IDownloader public class SymlinkDownloader(String uri, String destinationPath, String path, Provider? clientKind) : IDownloader
{ {
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete; public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress; public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
@ -62,7 +62,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path,
var shouldSearch = true; var shouldSearch = true;
// When resolving symlinks for AllDebrid, we know the exact file path, so we can skip the search. // When resolving symlinks for AllDebrid, we know the exact file path, so we can skip the search.
if (clientKind is Torrent.TorrentClientKind.AllDebrid or Torrent.TorrentClientKind.DebridLink) if (clientKind == Provider.AllDebrid)
{ {
var potentialFilePath = Path.Combine(rcloneMountPath, path); var potentialFilePath = Path.Combine(rcloneMountPath, path);

View file

@ -209,7 +209,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
if (!String.IsNullOrWhiteSpace(torrent.RdName)) if (!String.IsNullOrWhiteSpace(torrent.RdName))
{ {
// Alldebrid stores single file torrents at the root folder. // Alldebrid stores single file torrents at the root folder.
if (torrent.ClientKind == Torrent.TorrentClientKind.AllDebrid && torrent.Files.Count == 1) if (torrent.ClientKind == Provider.AllDebrid && torrent.Files.Count == 1)
{ {
torrentPath = Path.Combine(downloadPath, torrent.Files[0].Path); torrentPath = Path.Combine(downloadPath, torrent.Files[0].Path);
} else } else

View file

@ -8,13 +8,16 @@ using RdtClient.Service.Helpers;
using System.Web; using System.Web;
using RdtClient.Data.Models.Data; using RdtClient.Data.Models.Data;
using File = AllDebridNET.File; using File = AllDebridNET.File;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
using Torrent = RdtClient.Data.Models.Data.Torrent; using Torrent = RdtClient.Data.Models.Data.Torrent;
namespace RdtClient.Service.Services.TorrentClients; namespace RdtClient.Service.Services.TorrentClients;
public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHttpClientFactory httpClientFactory) : ITorrentClient public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHttpClientFactory httpClientFactory) : ITorrentClient
{ {
private static readonly Int64 SessionId = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
private static List<TorrentClientTorrent> _cache = [];
private static Int64 _sessionCounter = 0;
private AllDebridNETClient GetClient() private AllDebridNETClient GetClient()
{ {
try try
@ -49,30 +52,24 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHtt
private static TorrentClientTorrent Map(Magnet torrent) private static TorrentClientTorrent Map(Magnet torrent)
{ {
var files = GetFiles(torrent.Files);
return new() return new()
{ {
Id = torrent.Id.ToString(), Id = torrent.Id.ToString(),
Filename = torrent.Filename, Filename = torrent.Filename ?? "",
OriginalFilename = torrent.Filename, OriginalFilename = torrent.Filename,
Hash = torrent.Hash, Hash = torrent.Hash ?? "",
Bytes = torrent.Size, Bytes = torrent.Size ?? 0,
OriginalBytes = torrent.Size, OriginalBytes = torrent.Size ?? 0,
Host = null, Host = null,
Split = 0, Split = 0,
Progress = (Int64)Math.Round(torrent.Downloaded * 100.0 / torrent.Size), Progress = (Int64)Math.Round((torrent.Downloaded ?? 0) * 100.0 / (torrent.Size ?? 1)),
Status = torrent.Status, Status = torrent.Status,
StatusCode = torrent.StatusCode, StatusCode = torrent.StatusCode ?? 0,
Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate), Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate ?? 0),
Files = torrent.Links.Select((m, i) => new TorrentClientFile Files = files,
{ Links = [],
Path = GetFiles(m.Files), Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate ?? 0),
Bytes = m.Size,
Id = i,
Selected = true,
})
.ToList(),
Links = torrent.Links.Select(m => m.LinkUrl.ToString()).ToList(),
Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate),
Speed = torrent.DownloadSpeed, Speed = torrent.DownloadSpeed,
Seeders = torrent.Seeders Seeders = torrent.Seeders
}; };
@ -80,9 +77,31 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHtt
public async Task<IList<TorrentClientTorrent>> GetTorrents() public async Task<IList<TorrentClientTorrent>> GetTorrents()
{ {
var results = await GetClient().Magnet.StatusAllAsync(); var results = await GetClient().Magnet.StatusLiveAsync(SessionId, _sessionCounter);
return results.Select(Map).ToList(); _sessionCounter = results.Counter;
if (results.Fullsync == true)
{
_cache = (results.Magnets ?? []).Select(Map).ToList();
}
else
{
// Replace the existing items in the cache with the new ones based on the ID
foreach (var result in results.Magnets ?? [])
{
var existing = _cache.FirstOrDefault(m => m.Id == result.Id.ToString());
if (existing != null)
{
_cache.Remove(existing);
}
_cache.Add(Map(result));
}
}
_cache = _cache.Where(m => m.Status != null).ToList();
return _cache;
} }
public async Task<TorrentClientUser> GetUser() public async Task<TorrentClientUser> GetUser()
@ -177,7 +196,7 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHtt
torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files); torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files);
} }
torrent.ClientKind = Torrent.TorrentClientKind.AllDebrid; torrent.ClientKind = Provider.AllDebrid;
torrent.RdHost = torrentClientTorrent.Host; torrent.RdHost = torrentClientTorrent.Host;
torrent.RdSplit = torrentClientTorrent.Split; torrent.RdSplit = torrentClientTorrent.Split;
torrent.RdProgress = torrentClientTorrent.Progress; torrent.RdProgress = torrentClientTorrent.Progress;
@ -226,14 +245,9 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHtt
return null; return null;
} }
var magnet = await GetClient().Magnet.StatusAsync(torrent.RdId); var allFiles = await GetClient().Magnet.FilesAsync(Int64.Parse(torrent.RdId));
if (magnet == null) var files = GetFiles(allFiles);
{
return null;
}
var links = magnet.Links;
Log($"Getting download links", torrent); Log($"Getting download links", torrent);
@ -243,83 +257,74 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHtt
Log($"Determining which files are over {minFileSize} bytes", torrent); Log($"Determining which files are over {minFileSize} bytes", torrent);
links = links.Where(m => m.Size > minFileSize) files = files.Where(m => m.Bytes > minFileSize)
.ToList(); .ToList();
Log($"Found {links.Count} files that match the minimum file size criterea", torrent); Log($"Found {files.Count} files that match the minimum file size criterea", torrent);
} }
if (!String.IsNullOrWhiteSpace(torrent.IncludeRegex)) if (!String.IsNullOrWhiteSpace(torrent.IncludeRegex))
{ {
Log($"Using regular expression {torrent.IncludeRegex} to include only files matching this regex", torrent); Log($"Using regular expression {torrent.IncludeRegex} to include only files matching this regex", torrent);
var newLinks = new List<Link>(); var newFiles = new List<TorrentClientFile>();
foreach (var link in links) foreach (var file in files)
{ {
var path = GetFiles(link.Files); if (Regex.IsMatch(file.Path, torrent.IncludeRegex))
if (Regex.IsMatch(path, torrent.IncludeRegex))
{ {
Log($"* Including {path}", torrent); Log($"* Including {file.Path}", torrent);
newLinks.Add(link); newFiles.Add(file);
} }
else else
{ {
Log($"* Excluding {path}", torrent); Log($"* Excluding {file.Path}", torrent);
} }
} }
links = newLinks; files = newFiles;
Log($"Found {newLinks.Count} files that match the regex", torrent); Log($"Found {newFiles.Count} files that match the regex", torrent);
} }
else if (!String.IsNullOrWhiteSpace(torrent.ExcludeRegex)) else if (!String.IsNullOrWhiteSpace(torrent.ExcludeRegex))
{ {
Log($"Using regular expression {torrent.IncludeRegex} to ignore files matching this regex", torrent); Log($"Using regular expression {torrent.IncludeRegex} to ignore files matching this regex", torrent);
var newLinks = new List<Link>(); var newLinks = new List<TorrentClientFile>();
foreach (var link in links) foreach (var link in files)
{ {
var path = GetFiles(link.Files); if (!Regex.IsMatch(link.Path, torrent.ExcludeRegex))
if (!Regex.IsMatch(path, torrent.ExcludeRegex))
{ {
Log($"* Including {path}", torrent); Log($"* Including {link.Path}", torrent);
newLinks.Add(link); newLinks.Add(link);
} }
else else
{ {
Log($"* Excluding {path}", torrent); Log($"* Excluding {link.Path}", torrent);
} }
} }
links = newLinks; files = newLinks;
Log($"Found {newLinks.Count} files that match the regex", torrent); Log($"Found {newLinks.Count} files that match the regex", torrent);
} }
if (links.Count == 0) if (files.Count == 0)
{ {
Log($"Filtered all files out! Downloading ALL files instead!", torrent); Log($"Filtered all files out! Downloading ALL files instead!", torrent);
links = magnet.Links; files = GetFiles(allFiles);
} }
if (logger.IsEnabled(LogLevel.Debug)) Log($"Selecting links:");
foreach (var file in files)
{ {
Log($"Selecting links:"); Log($"{file.Path} ({file.Bytes}b) {file.DownloadLink}");
foreach (var link in links)
{
Log($"{GetFiles(link.Files)} ({link.Size}b) {link.LinkUrl}");
}
} }
Log("", torrent); return files.Where(m => m.DownloadLink != null).Select(m => m.DownloadLink!.ToString()).ToList();
return links.Select(m => m.LinkUrl.ToString()).ToList();
} }
public Task<String> GetFileName(String downloadUrl) public Task<String> GetFileName(String downloadUrl)
@ -340,65 +345,41 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHtt
return Map(result); return Map(result);
} }
private static String GetFiles(IList<File> files) private static List<TorrentClientFile> GetFiles(List<File>? files, String parentPath = "")
{ {
var result = new List<String>(); if (files == null)
foreach (var file in files)
{ {
if (!String.IsNullOrWhiteSpace(file.N)) return [];
{ }
result.Add(file.N);
}
if (file.E != null && file.E.Value.PurpleEArray != null && file.E.Value.PurpleEArray.Count > 0) return files.SelectMany(file =>
{
var currentPath = String.IsNullOrEmpty(parentPath)
? file.FolderOrFileName
: Path.Combine(parentPath, file.FolderOrFileName);
var result = new List<TorrentClientFile>();
// If it's a file (has size)
if (file.Size.HasValue)
{ {
if (file.E.Value.PurpleEArray.Count != 1) result.Add(new()
{ {
throw new("Unexpected number of nested files"); Path = currentPath,
} Bytes = file.Size.Value,
DownloadLink = file.DownloadLink
result.AddRange(GetFiles(file.E.Value.PurpleEArray)); });
} }
}
return String.Join("/", result); // Process sub-nodes if they exist
} if (file.SubNodes != null)
private static List<String> GetFiles(IList<FileE1> files)
{
var result = new List<String>();
foreach (var file in files)
{
if (!String.IsNullOrWhiteSpace(file.N))
{ {
result.Add(file.N); result.AddRange(GetFiles(file.SubNodes, currentPath));
} }
if (file.E != null && file.E.Count > 0) return result;
{ }).ToList();
result.AddRange(GetFiles(file.E));
}
}
return result;
}
private static List<String> GetFiles(IList<FileE2> files)
{
var result = new List<String>();
foreach (var file in files)
{
if (!String.IsNullOrWhiteSpace(file.N))
{
result.Add(file.N);
}
}
return result;
} }
private void Log(String message, Torrent? torrent = null) private void Log(String message, Torrent? torrent = null)

View file

@ -162,7 +162,7 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files); torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files);
} }
torrent.ClientKind = Torrent.TorrentClientKind.Premiumize; torrent.ClientKind = Provider.Premiumize;
torrent.RdHost = torrentClientTorrent.Host; torrent.RdHost = torrentClientTorrent.Host;
torrent.RdSplit = torrentClientTorrent.Split; torrent.RdSplit = torrentClientTorrent.Split;
torrent.RdProgress = torrentClientTorrent.Progress; torrent.RdProgress = torrentClientTorrent.Progress;

View file

@ -295,7 +295,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files); torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files);
} }
torrent.ClientKind = Data.Models.Data.Torrent.TorrentClientKind.RealDebrid; torrent.ClientKind = Provider.RealDebrid;
torrent.RdHost = torrentClientTorrent.Host; torrent.RdHost = torrentClientTorrent.Host;
torrent.RdSplit = torrentClientTorrent.Split; torrent.RdSplit = torrentClientTorrent.Split;
torrent.RdProgress = torrentClientTorrent.Progress; torrent.RdProgress = torrentClientTorrent.Progress;

View file

@ -230,7 +230,7 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files); torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files);
} }
torrent.ClientKind = Torrent.TorrentClientKind.TorBox; torrent.ClientKind = Provider.TorBox;
torrent.RdHost = rdTorrent.Host; torrent.RdHost = rdTorrent.Host;
torrent.RdSplit = rdTorrent.Split; torrent.RdSplit = rdTorrent.Split;
torrent.RdProgress = rdTorrent.Progress; torrent.RdProgress = rdTorrent.Progress;

View file

@ -29,15 +29,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="9.0.1" /> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="9.0.1" /> <PackageReference Include="Microsoft.Extensions.Identity.Core" Version="9.0.2" />
<PackageReference Include="Serilog" Version="4.2.0" /> <PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />