Merge remote-tracking branch 'rogerfar/master'
This commit is contained in:
commit
f9335be163
24 changed files with 435 additions and 163 deletions
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.0.90] - 2024-12-06
|
||||
### Changed
|
||||
- Download individual files from Torbox instead of a zip file.
|
||||
|
||||
### Removed
|
||||
- Removed ability to select instant files from AllDebrid.
|
||||
|
||||
## [2.0.89] - 2024-11-24
|
||||
### Changed
|
||||
- Disabled selecting of files as Real-Debrid was the only provider that supported that.
|
||||
|
||||
## [2.0.88] - 2024-11-24
|
||||
### Changed
|
||||
- Catch disabled instant availability endpoint from Real Debrid.
|
||||
|
||||
## [2.0.87] - 2024-11-18
|
||||
### Added
|
||||
- Torbox support.
|
||||
- qBittorrent API authentication when no authentication is used.
|
||||
### Changed
|
||||
- .NET version changed to .NET 9.
|
||||
- Changed download limit to be split by active torrents.
|
||||
|
||||
## [2.0.86] - 2024-09-03
|
||||
### Changed
|
||||
- Add potential fix for BASE_PATH.
|
||||
|
|
|
|||
14
Dockerfile
14
Dockerfile
|
|
@ -21,7 +21,7 @@ RUN \
|
|||
RUN ls -FCla /appclient/root
|
||||
|
||||
# Stage 2 - Build the backend
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim-amd64 AS dotnet-build-env
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim-amd64 AS dotnet-build-env
|
||||
ARG TARGETPLATFORM
|
||||
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
|
||||
ARG BUILDPLATFORM
|
||||
|
|
@ -66,14 +66,14 @@ RUN \
|
|||
|
||||
RUN \
|
||||
if [ "$TARGETPLATFORM" = "linux/arm/v7" ] ; then \
|
||||
wget https://download.visualstudio.microsoft.com/download/pr/c3bf3103-efdb-42e0-af55-bbf861a4215b/dc22eda8877933b8c6569e3823f18d21/aspnetcore-runtime-8.0.0-linux-musl-arm64.tar.gz && \
|
||||
tar zxf aspnetcore-runtime-8.0.0-linux-musl-arm64.tar.gz -C /usr/share/dotnet ; \
|
||||
wget https://download.visualstudio.microsoft.com/download/pr/59a041e1-921e-405e-8092-95333f80f9ca/63e83e3feb70e05ca05ed5db3c579be2/aspnetcore-runtime-9.0.0-linux-musl-arm.tar.gz && \
|
||||
tar zxf aspnetcore-runtime-9.0.0-linux-musl-arm.tar.gz -C /usr/share/dotnet ; \
|
||||
elif [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \
|
||||
wget https://download.visualstudio.microsoft.com/download/pr/c3bf3103-efdb-42e0-af55-bbf861a4215b/dc22eda8877933b8c6569e3823f18d21/aspnetcore-runtime-8.0.0-linux-musl-arm64.tar.gz && \
|
||||
tar zxf aspnetcore-runtime-8.0.0-linux-musl-arm64.tar.gz -C /usr/share/dotnet ; \
|
||||
wget https://download.visualstudio.microsoft.com/download/pr/e137f557-83cb-4f55-b1c8-e5f59ccd3cba/b8ba6f2ab96d0961757b71b00c201f31/aspnetcore-runtime-9.0.0-linux-musl-arm64.tar.gz && \
|
||||
tar zxf aspnetcore-runtime-9.0.0-linux-musl-arm64.tar.gz -C /usr/share/dotnet ; \
|
||||
else \
|
||||
wget https://download.visualstudio.microsoft.com/download/pr/7aa33fc7-07fe-48c2-8e44-a4bfb4928535/3b96ec50970eee414895ef3a5b188bcd/aspnetcore-runtime-8.0.0-linux-musl-x64.tar.gz && \
|
||||
tar zxf aspnetcore-runtime-8.0.0-linux-musl-x64.tar.gz -C /usr/share/dotnet ; \
|
||||
wget https://download.visualstudio.microsoft.com/download/pr/86d7a513-fe71-4f37-b9ec-fdcf5566cce8/e72574fc82d7496c73a61f411d967d8e/aspnetcore-runtime-9.0.0-linux-musl-x64.tar.gz && \
|
||||
tar zxf aspnetcore-runtime-9.0.0-linux-musl-x64.tar.gz -C /usr/share/dotnet ; \
|
||||
fi
|
||||
|
||||
RUN \
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ This is a web interface to manage your torrents on Real-Debrid, AllDebrid or Pre
|
|||
- Download all files from Real-Debrid, AllDebrid or Premiumize to your local machine automatically
|
||||
- Unpack all files when finished downloading
|
||||
- Implements a fake qBittorrent API so you can hook up other applications like Sonarr, Radarr or Couchpotato.
|
||||
- Built with Angular 15 and .NET 8
|
||||
- Built with Angular 15 and .NET 9
|
||||
|
||||
**You will need a Premium service at Real-Debrid, AllDebrid or Premiumize!**
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ Instead of running in Docker you can install it as a service in Windows or Linux
|
|||
|
||||
## Windows Service
|
||||
|
||||
1. Make sure you have the ASP.NET Core Runtime 8 installed: [https://dotnet.microsoft.com/download/dotnet/8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
|
||||
1. Make sure you have the **ASP.NET Core Runtime 9.0.0** installed: [https://dotnet.microsoft.com/download/dotnet/9.0](https://dotnet.microsoft.com/download/dotnet/9.0)
|
||||
2. Get the latest zip file from the Releases page and extract it to your host.
|
||||
3. Open the `appsettings.json` file and replace the `LogLevel` `Path` to a path on your host.
|
||||
4. In `appsettings.json` replace the `Database` `Path` to a path on your host.
|
||||
|
|
@ -52,7 +52,7 @@ Instead of running in Docker you can install it as a service in Linux.
|
|||
|
||||
```rm packages-microsoft-prod.deb```
|
||||
|
||||
```sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.0```
|
||||
```sudo apt-get update && sudo apt-get install -y dotnet-sdk-9.0```
|
||||
|
||||
2. Get latest archive from [releases](https://github.com/rogerfar/rdt-client/releases):
|
||||
```wget <zip_url>```
|
||||
|
|
@ -188,7 +188,7 @@ By default the application runs in the root of your hosted address (i.e. https:/
|
|||
- NodeJS
|
||||
- NPM
|
||||
- Angular CLI
|
||||
- .NET 8
|
||||
- .NET 9
|
||||
- Visual Studio 2022
|
||||
- (optional) Resharper
|
||||
|
||||
|
|
|
|||
|
|
@ -72,35 +72,6 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Post Torrent Download Action</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="downloadAction" [disabled]="provider === 'AllDebrid' || provider === 'Premiumize'">
|
||||
<option [ngValue]="0">Download all files</option>
|
||||
<option [ngValue]="1">Download all available files</option>
|
||||
<option [ngValue]="2">Manually pick files</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="help">When a torrent is fully downloaded on the provider, perform this action.</p>
|
||||
<p class="help" *ngIf="provider === 'AllDebrid'">
|
||||
This option is only available for RealDebrid. AllDebrid and Premiumize will always download the full torrent.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="notification is-success is-light"
|
||||
*ngIf="provider === 'AllDebrid' && availableFiles && availableFiles.length > 0"
|
||||
>
|
||||
This torrent is available for immediate download.
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="notification is-warning is-light"
|
||||
*ngIf="provider === 'AllDebrid' && availableFiles && availableFiles.length === 0"
|
||||
>
|
||||
This torrent is not available for immediate download.
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Minimum file size to download</label>
|
||||
<div class="control">
|
||||
|
|
@ -221,39 +192,6 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex *ngIf="provider === 'RealDebrid'">
|
||||
<div class="field">
|
||||
<label class="label">Available files</label>
|
||||
<p class="help">
|
||||
These files are available for immediate download from your debrid provider. <br />
|
||||
It is possible that there are more files in the torrent, which are not shown here.<br />
|
||||
</p>
|
||||
<div class="scroll-container">
|
||||
<div class="field" *ngIf="downloadAction === 2">
|
||||
<label class="checkbox is-fullwidth-label">
|
||||
<input type="checkbox" [checked]="allSelected" (change)="downloadFileCheckedAll()" />
|
||||
Select all
|
||||
</label>
|
||||
</div>
|
||||
<div class="field" *ngIf="downloadAction === 2 && availableFiles !== null">
|
||||
<label class="checkbox is-fullwidth-label" *ngFor="let file of availableFiles">
|
||||
<input
|
||||
type="checkbox"
|
||||
[checked]="downloadFiles[file.filename]"
|
||||
(change)="downloadFileChecked(file.filename)"
|
||||
/>
|
||||
{{ file.filename }} ({{ file.filesize | filesize }})
|
||||
</label>
|
||||
</div>
|
||||
<div class="field" *ngIf="downloadAction !== 2 && availableFiles !== null">
|
||||
<label class="is-fullwidth-label is-block" *ngFor="let file of availableFiles">
|
||||
<span [ngClass]="{ 'strike-through': isRegexExcluded(file) }">{{ file.filename }}</span>
|
||||
<span *ngIf="file.filesize > 0">({{ file.filesize | filesize }})</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
<a class="navbar-item" routerLink="profile"> Profile </a>
|
||||
<a class="navbar-item" (click)="logout()"> Logout </a>
|
||||
<hr class="navbar-divider" />
|
||||
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version 2.0.86</a>
|
||||
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version 2.0.90</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ export class NavbarComponent implements OnInit {
|
|||
public profile: Profile;
|
||||
public providerLink: string;
|
||||
|
||||
constructor(private settingsService: SettingsService, private authService: AuthService, private router: Router) {}
|
||||
constructor(
|
||||
private settingsService: SettingsService,
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsService.getProfile().subscribe((result) => {
|
||||
|
|
@ -31,6 +35,9 @@ export class NavbarComponent implements OnInit {
|
|||
case 'Premiumize':
|
||||
this.providerLink = 'https://www.premiumize.me/';
|
||||
break;
|
||||
case 'TorBox':
|
||||
this.providerLink = 'https://torbox.app/';
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -40,7 +47,7 @@ export class NavbarComponent implements OnInit {
|
|||
() => {
|
||||
this.router.navigate(['/login']);
|
||||
},
|
||||
(err) => {}
|
||||
(err) => {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
<div class="notification is-primary">
|
||||
To be able to use the Real-Debrid Client you need a premium subscription to download torrents.
|
||||
<br /><br />
|
||||
Not premium yet? You have the choice of 2 providers:
|
||||
Not premium yet? You have the choice of 4 providers:
|
||||
<br />
|
||||
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener"
|
||||
>Use this link to sign up to Real-Debrid.</a
|
||||
|
|
@ -55,6 +55,8 @@
|
|||
<a href="https://www.premiumize.me/" target="_blank" rel="noopener"
|
||||
>Use this link to sign up to Premiumize.</a
|
||||
>
|
||||
<a href="https://torbox.app/" target="_blank" rel="noopener"
|
||||
>Use this link to sign up to TorBox.</a>
|
||||
<br />
|
||||
<small>(Referal links)</small>
|
||||
</div>
|
||||
|
|
@ -65,6 +67,7 @@
|
|||
<option [ngValue]="0">Real-Debrid</option>
|
||||
<option [ngValue]="1">AllDebrid</option>
|
||||
<option [ngValue]="2">Premiumize</option>
|
||||
<option [ngValue]="3">TorBox</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -91,6 +94,10 @@
|
|||
>https://www.premiumize.me/account</a
|
||||
>.
|
||||
</p>
|
||||
<p class="help" *ngIf="provider === 3">
|
||||
You can find your API key here:
|
||||
<a href="https://torbox.app/settings" target="_blank" rel="noopener">https://torbox.app/settings</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "rdt-client",
|
||||
"version": "2.0.86",
|
||||
"version": "2.0.90",
|
||||
"description": "This is a web interface to manage your torrents on Real-Debrid.",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,5 +11,8 @@ public enum Provider
|
|||
AllDebrid,
|
||||
|
||||
[Description("Premiumize")]
|
||||
Premiumize
|
||||
Premiumize,
|
||||
|
||||
[Description("TorBox")]
|
||||
TorBox
|
||||
}
|
||||
|
|
@ -155,10 +155,11 @@ http://127.0.0.1:6800/jsonrpc.")]
|
|||
public class DbSettingsProvider
|
||||
{
|
||||
[DisplayName("Provider")]
|
||||
[Description(@"The following 3 providers are supported:
|
||||
[Description(@"The following 4 providers are supported:
|
||||
<a href=""https://real-debrid.com/?id=1348683"" target=""_blank"" rel=""noopener"">https://real-debrid.com</a>
|
||||
<a href=""https://alldebrid.com/?uid=2v91l&lang=en"" target=""_blank"" rel=""noopener"">https://alldebrid.com</a>
|
||||
<a href=""https://www.premiumize.me/"" target=""_blank"" rel=""noopener"">https://www.premiumize.me/</a>
|
||||
<a href=""https://torbox.app/"" target=""_blank"" rel=""noopener"">https://torbox.app/</a>
|
||||
At this point only 1 provider can be used at the time.")]
|
||||
public Provider Provider { get; set; } = Provider.RealDebrid;
|
||||
|
||||
|
|
@ -168,7 +169,9 @@ At this point only 1 provider can be used at the time.")]
|
|||
or
|
||||
<a href=""""https://alldebrid.com/apikeys/"""" target=""""_blank"""" rel=""""noopener"""">https://alldebrid.com/apikeys/</a>
|
||||
or
|
||||
<a href=""https://www.premiumize.me/account/"" target=""_blank"" rel=""noopener"">https://www.premiumize.me/account/</a>")]
|
||||
<a href=""https://www.premiumize.me/account/"" target=""_blank"" rel=""noopener"">https://www.premiumize.me/account/</a>
|
||||
or
|
||||
<a href=""""https://torbox.app/settings/"""" target=""""_blank"""" rel=""""noopener"""">https://torbox.app/settings/</a>")]
|
||||
public String ApiKey { get; set; } = "";
|
||||
|
||||
[DisplayName("Automatically import and process torrents added to provider")]
|
||||
|
|
|
|||
|
|
@ -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.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public static class DiConfig
|
|||
services.AddScoped<RemoteService>();
|
||||
services.AddScoped<RealDebridTorrentClient>();
|
||||
services.AddScoped<Settings>();
|
||||
services.AddScoped<TorBoxTorrentClient>();
|
||||
services.AddScoped<Torrents>();
|
||||
services.AddScoped<TorrentRunner>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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,17 +11,18 @@
|
|||
<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.3.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.2.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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class InternalDownloader : IDownloader
|
|||
|
||||
_uri = uri;
|
||||
_filePath = filePath;
|
||||
|
||||
|
||||
_downloadConfiguration = new();
|
||||
|
||||
SetSettings();
|
||||
|
|
@ -131,14 +131,14 @@ public class InternalDownloader : IDownloader
|
|||
{
|
||||
settingDownloadParallelCount = 1;
|
||||
}
|
||||
|
||||
|
||||
var settingDownloadMaxSpeed = Settings.Get.DownloadClient.MaxSpeed;
|
||||
|
||||
if (settingDownloadMaxSpeed <= 0)
|
||||
{
|
||||
settingDownloadMaxSpeed = 0;
|
||||
}
|
||||
|
||||
settingDownloadMaxSpeed /= Math.Max(TorrentRunner.ActiveDownloadClients.Count, 1);
|
||||
settingDownloadMaxSpeed = settingDownloadMaxSpeed * 1024 * 1024;
|
||||
|
||||
var settingDownloadTimeout = Settings.Get.DownloadClient.Timeout;
|
||||
|
|
|
|||
|
|
@ -118,23 +118,9 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHtt
|
|||
return resultId;
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
public Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
var isAvailable = await GetClient().Magnet.InstantAvailabilityAsync(hash);
|
||||
|
||||
if (isAvailable)
|
||||
{
|
||||
return
|
||||
[
|
||||
new()
|
||||
{
|
||||
Filename = "All files",
|
||||
Filesize = 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
return Task.FromResult<IList<TorrentClientAvailableFile>>([]);
|
||||
}
|
||||
|
||||
public Task SelectFiles(Torrent torrent)
|
||||
|
|
|
|||
|
|
@ -117,8 +117,7 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
|
|||
|
||||
public Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
var result = new List<TorrentClientAvailableFile>();
|
||||
return Task.FromResult<IList<TorrentClientAvailableFile>>(result);
|
||||
return Task.FromResult<IList<TorrentClientAvailableFile>>([]);
|
||||
}
|
||||
|
||||
public Task SelectFiles(Torrent torrent)
|
||||
|
|
|
|||
|
|
@ -137,49 +137,27 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
|
|||
return result.Id;
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
public Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
var result = await GetClient().Torrents.GetAvailableFiles(hash);
|
||||
|
||||
var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values);
|
||||
|
||||
var groups = files.Where(m => m.Filename != null).GroupBy(m => $"{m.Filename}-{m.Filesize}");
|
||||
|
||||
var torrentClientAvailableFiles = groups.Select(m => new TorrentClientAvailableFile
|
||||
{
|
||||
Filename = m.First().Filename!,
|
||||
Filesize = m.First().Filesize
|
||||
}).ToList();
|
||||
|
||||
return torrentClientAvailableFiles;
|
||||
return Task.FromResult<IList<TorrentClientAvailableFile>>([]);
|
||||
}
|
||||
|
||||
public async Task SelectFiles(Data.Models.Data.Torrent torrent)
|
||||
{
|
||||
var files = torrent.Files;
|
||||
IList<TorrentClientFile> files;
|
||||
|
||||
Log("Seleting files", torrent);
|
||||
|
||||
if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles)
|
||||
{
|
||||
Log($"Determining which files are already available on RealDebrid", torrent);
|
||||
|
||||
var availableFiles = await GetAvailableFiles(torrent.Hash);
|
||||
|
||||
Log($"Found {files.Count}/{torrent.Files.Count} available files on RealDebrid", torrent);
|
||||
|
||||
files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
|
||||
{
|
||||
Log("Selecting all files", torrent);
|
||||
files = [.. torrent.Files];
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
|
||||
if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
|
||||
{
|
||||
Log("Selecting manual selected files", torrent);
|
||||
files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("Selecting all files", torrent);
|
||||
files = [.. torrent.Files];
|
||||
}
|
||||
|
||||
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,319 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using TorBoxNET;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
|
||||
namespace RdtClient.Service.Services.TorrentClients;
|
||||
|
||||
public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClientFactory httpClientFactory) : ITorrentClient
|
||||
{
|
||||
private TimeSpan? _offset;
|
||||
private TorBoxNetClient GetClient()
|
||||
{
|
||||
try
|
||||
{
|
||||
var apiKey = Settings.Get.Provider.ApiKey;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
throw new("TorBox API Key not set in the settings");
|
||||
}
|
||||
|
||||
var httpClient = httpClientFactory.CreateClient();
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
||||
|
||||
var torBoxNetClient = new TorBoxNetClient(null, httpClient, 5);
|
||||
torBoxNetClient.UseApiAuthentication(apiKey);
|
||||
|
||||
// Get the server time to fix up the timezones on results
|
||||
if (_offset == null)
|
||||
{
|
||||
var serverTime = DateTimeOffset.UtcNow;
|
||||
_offset = serverTime.Offset;
|
||||
}
|
||||
|
||||
return torBoxNetClient;
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
foreach (var inner in ae.InnerExceptions)
|
||||
{
|
||||
logger.LogError(inner, $"The connection to RealDebrid has failed: {inner.Message}");
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
||||
{
|
||||
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private TorrentClientTorrent Map(TorrentInfoResult torrent)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = torrent.Hash,
|
||||
Filename = torrent.Name,
|
||||
OriginalFilename = torrent.Name,
|
||||
Hash = torrent.Hash,
|
||||
Bytes = torrent.Size,
|
||||
OriginalBytes = torrent.Size,
|
||||
Host = torrent.DownloadPresent.ToString(),
|
||||
Split = 0,
|
||||
Progress = (Int64)((torrent.Progress) * 100.0),
|
||||
Status = torrent.DownloadState,
|
||||
Added = ChangeTimeZone(torrent.CreatedAt)!.Value,
|
||||
Files = (torrent.Files ?? []).Select(m => new TorrentClientFile
|
||||
{
|
||||
Path = String.Join("/", m.Name.Split('/').Skip(1)),
|
||||
Bytes = m.Size,
|
||||
Id = m.Id,
|
||||
Selected = true
|
||||
}).ToList(),
|
||||
Links = [],
|
||||
Ended = ChangeTimeZone(torrent.UpdatedAt),
|
||||
Speed = torrent.DownloadSpeed,
|
||||
Seeders = torrent.Seeds,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||
{
|
||||
var torrents = new List<TorrentInfoResult>();
|
||||
|
||||
var currentTorrents = await GetClient().Torrents.GetCurrentAsync(true);
|
||||
if (currentTorrents != null)
|
||||
{
|
||||
torrents.AddRange(currentTorrents);
|
||||
}
|
||||
|
||||
var queuedTorrents = await GetClient().Torrents.GetQueuedAsync(true);
|
||||
if (queuedTorrents != null)
|
||||
{
|
||||
torrents.AddRange(queuedTorrents);
|
||||
}
|
||||
|
||||
return torrents.Select(Map).ToList();
|
||||
}
|
||||
|
||||
public async Task<TorrentClientUser> GetUser()
|
||||
{
|
||||
var user = await GetClient().User.GetAsync(false);
|
||||
|
||||
return new()
|
||||
{
|
||||
Username = user.Data!.Email,
|
||||
Expiration = user.Data!.Plan != 0 ? user.Data!.PremiumExpiresAt!.Value : null
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<String> AddMagnet(String magnetLink)
|
||||
{
|
||||
// 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;
|
||||
|
||||
var seed = 3;
|
||||
var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, seed, false);
|
||||
|
||||
if (result.Error == "ACTIVE_LIMIT")
|
||||
{
|
||||
var magnetLinkInfo = MonoTorrent.MagnetLink.Parse(magnetLink);
|
||||
return magnetLinkInfo.InfoHashes.V1!.ToHex().ToLowerInvariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
return result.Data!.Hash!;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<String> AddFile(Byte[] bytes)
|
||||
{
|
||||
// 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 result = await GetClient().Torrents.AddFileAsync(bytes, seed);
|
||||
if (result.Error == "ACTIVE_LIMIT")
|
||||
{
|
||||
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)
|
||||
{
|
||||
var availability = await GetClient().Torrents.GetAvailabilityAsync(hash, listFiles: true);
|
||||
|
||||
if (availability.Data != null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task Delete(String torrentId)
|
||||
{
|
||||
await GetClient().Torrents.ControlAsync(torrentId, "delete");
|
||||
}
|
||||
|
||||
public async Task<String> Unrestrict(String link)
|
||||
{
|
||||
var segments = link.Split('/');
|
||||
|
||||
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)
|
||||
{
|
||||
throw new("Unrestrict returned an invalid download");
|
||||
}
|
||||
|
||||
return result.Data!;
|
||||
}
|
||||
|
||||
public async Task<Data.Models.Data.Torrent> UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent? torrentClientTorrent)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (torrent.RdId == null)
|
||||
{
|
||||
return torrent;
|
||||
}
|
||||
|
||||
var rdTorrent = await GetInfo(torrent.Hash) ?? throw new($"Resource not found");
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.Filename))
|
||||
{
|
||||
torrent.RdName = rdTorrent.Filename;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.OriginalFilename))
|
||||
{
|
||||
torrent.RdName = rdTorrent.OriginalFilename;
|
||||
}
|
||||
|
||||
if (rdTorrent.Bytes > 0)
|
||||
{
|
||||
torrent.RdSize = rdTorrent.Bytes;
|
||||
}
|
||||
else if (rdTorrent.OriginalBytes > 0)
|
||||
{
|
||||
torrent.RdSize = rdTorrent.OriginalBytes;
|
||||
}
|
||||
|
||||
if (rdTorrent.Files != null)
|
||||
{
|
||||
torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files);
|
||||
}
|
||||
|
||||
torrent.RdHost = rdTorrent.Host;
|
||||
torrent.RdSplit = rdTorrent.Split;
|
||||
torrent.RdProgress = rdTorrent.Progress;
|
||||
torrent.RdAdded = rdTorrent.Added;
|
||||
torrent.RdEnded = rdTorrent.Ended;
|
||||
torrent.RdSpeed = rdTorrent.Speed;
|
||||
torrent.RdSeeders = rdTorrent.Seeders;
|
||||
torrent.RdStatusRaw = rdTorrent.Status;
|
||||
|
||||
if (rdTorrent.Host == "True")
|
||||
{
|
||||
torrent.RdStatus = TorrentStatus.Finished;
|
||||
}
|
||||
else
|
||||
{
|
||||
torrent.RdStatus = rdTorrent.Status switch
|
||||
{
|
||||
"queued" => TorrentStatus.Processing,
|
||||
"metaDL" => TorrentStatus.Processing,
|
||||
"checking" => TorrentStatus.Processing,
|
||||
"checkingResumeData" => TorrentStatus.Processing,
|
||||
"paused" => TorrentStatus.Downloading,
|
||||
"downloading" => TorrentStatus.Downloading,
|
||||
"completed" => TorrentStatus.Downloading,
|
||||
"uploading" => TorrentStatus.Downloading,
|
||||
"uploading (no peers)" => TorrentStatus.Downloading,
|
||||
"stalled" => TorrentStatus.Downloading,
|
||||
"stalled (no seeds)" => TorrentStatus.Downloading,
|
||||
"processing" => TorrentStatus.Downloading,
|
||||
"cached" => TorrentStatus.Finished,
|
||||
_ => TorrentStatus.Error
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.Message == "Resource not found")
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
public async Task<IList<String>?> GetDownloadLinks(Data.Models.Data.Torrent torrent)
|
||||
{
|
||||
var files = new List<String>();
|
||||
|
||||
var torrentId = await GetClient().Torrents.GetHashInfoAsync(torrent.Hash, skipCache: true);
|
||||
|
||||
foreach (var file in torrent.Files)
|
||||
{
|
||||
var newFile = $"https://torbox.app/fakedl/{torrentId?.Id}/{file.Id}";
|
||||
files.Add(newFile);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
|
||||
{
|
||||
if (_offset == null)
|
||||
{
|
||||
return dateTimeOffset;
|
||||
}
|
||||
|
||||
return dateTimeOffset?.Subtract(_offset.Value).ToOffset(_offset.Value);
|
||||
}
|
||||
|
||||
private async Task<TorrentClientTorrent> GetInfo(String torrentHash)
|
||||
{
|
||||
var result = await GetClient().Torrents.GetHashInfoAsync(torrentHash, skipCache: true);
|
||||
|
||||
return Map(result!);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,8 @@ public class Torrents(
|
|||
Downloads downloads,
|
||||
AllDebridTorrentClient allDebridTorrentClient,
|
||||
PremiumizeTorrentClient premiumizeTorrentClient,
|
||||
RealDebridTorrentClient realDebridTorrentClient)
|
||||
RealDebridTorrentClient realDebridTorrentClient,
|
||||
TorBoxTorrentClient torBoxTorrentClient)
|
||||
{
|
||||
private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1);
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ public class Torrents(
|
|||
Provider.Premiumize => premiumizeTorrentClient,
|
||||
Provider.RealDebrid => realDebridTorrentClient,
|
||||
Provider.AllDebrid => allDebridTorrentClient,
|
||||
Provider.TorBox => torBoxTorrentClient,
|
||||
_ => throw new("Invalid Provider")
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -21,6 +22,11 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
|||
{
|
||||
logger.LogDebug($"Auth login");
|
||||
|
||||
if (Settings.Get.General.AuthenticationType == AuthenticationType.None)
|
||||
{
|
||||
return Ok("Ok.");
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(request.UserName) || String.IsNullOrEmpty(request.Password))
|
||||
{
|
||||
return Ok("Fails.");
|
||||
|
|
|
|||
|
|
@ -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,10 +1,10 @@
|
|||
<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>
|
||||
<Version>2.0.90</Version>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
|
@ -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.2.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