Skip to content

Commit

Permalink
Add unraring progress and default auto download / auto remove options.
Browse files Browse the repository at this point in the history
  • Loading branch information
rogerfar committed Apr 11, 2020
1 parent bfba5c9 commit f4ae266
Show file tree
Hide file tree
Showing 17 changed files with 304 additions and 82 deletions.
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
# Real-Debrid Torrent Client

Manage your Real-Debrid torrents easily with this client.
This is a web interface to manage your torrents on Real-Debrid. It supports the following features:

- Add new torrents through magnet or files
- Download all files from Real Debrid 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 or Couchpotato.
- Build with Angular 9 and .NET Core 3.1

**You will need a Premium service at Real-Debrid!**

## Installation

1. Unpack the latest release from the releases folder and run `startup.bat`. This will start the application on port 6500.
2. Browse to http://127.0.0.1:6500
3. The very first credentials you type in will be remembered for future logins.
4. Click on Settings on the top and enter your Real-Debrid API key.
5. Change your download path if needed.
6. To install as service on Windows, exit the console and run `serviceinstall.bat` as administrator.

## Troubleshooting

- If you forgot your logins simply delete the `database.db` and restart the service.

## Connecting Sonarr

1. Login to Sonarr and click Settings
2. Go to the Download Client tab and click the plus to add
3. Click "qBittorrent" in the list
4. Enter the IP or hostname of the RealDebridClient in the Host field
5. Enter the port 6500 in the Port field
6. Enter your Username/Password you setup in step 3 above in the Username/Password field.
7. Leave the other settings as is.
8. Hit Test and then Save if all is well.
9. Sonarr will now think you have a regular Torrent client hooked up.

Notice: the progress and ETA reported in Sonarr's Activity tab will not be very accurate, but it will report the torrent as completed so it can be processed by Sonarr when done downloading.

## Build instructions

1. Open the client folder project in VS Code and run `npm install`
2. To debug run `ng serve`, to build run `ng build --prod`
3. Open the Visual Studio 2019 project `RdtClient.sln` and `Publish`
4. The result is found in `\rdt-client\server\RdtClient.Web\bin\Release\netcoreapp3.1\publish`
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@
</label>
</div>
</div>
<hr />
<div class="field">
<label class="checkbox">
<input type="checkbox" [(ngModel)]="autoDownload" />
Download torrent to host when finished downloading on Real-Debrid
</label>
<label class="checkbox">
<input type="checkbox" [(ngModel)]="autoDelete" />
Remove torrent when finished downloading on host
</label>
</div>
<div class="notification is-danger is-light" *ngIf="error?.length > 0">
Cannot add torrent: {{ error }}
</div>
Expand Down
48 changes: 29 additions & 19 deletions client/src/app/navbar/add-new-torrent/add-new-torrent.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TorrentService } from 'src/app/torrent.service';
@Component({
selector: 'app-add-new-torrent',
templateUrl: './add-new-torrent.component.html',
styleUrls: ['./add-new-torrent.component.scss']
styleUrls: ['./add-new-torrent.component.scss'],
})
export class AddNewTorrentComponent implements OnInit {
@Input()
Expand All @@ -24,6 +24,9 @@ export class AddNewTorrentComponent implements OnInit {

public fileName: string;
public magnetLink: string;
public autoDownload: boolean;
public autoDelete: boolean;

public saving = false;
public error: string;

Expand All @@ -36,6 +39,9 @@ export class AddNewTorrentComponent implements OnInit {
public reset(): void {
this.fileName = '';
this.magnetLink = '';
this.autoDelete = false;
this.autoDownload = true;

this.saving = false;
this.selectedFile = null;
this.error = null;
Expand All @@ -60,25 +66,29 @@ export class AddNewTorrentComponent implements OnInit {
this.error = null;

if (this.magnetLink) {
this.torrentService.uploadMagnet(this.magnetLink).subscribe(
() => {
this.cancel();
},
err => {
this.error = err.error;
this.saving = false;
}
);
this.torrentService
.uploadMagnet(this.magnetLink, this.autoDownload, this.autoDelete)
.subscribe(
() => {
this.cancel();
},
(err) => {
this.error = err.error;
this.saving = false;
}
);
} else if (this.selectedFile) {
this.torrentService.uploadFile(this.selectedFile).subscribe(
() => {
this.cancel();
},
err => {
this.error = err.error;
this.saving = false;
}
);
this.torrentService
.uploadFile(this.selectedFile, this.autoDownload, this.autoDelete)
.subscribe(
() => {
this.cancel();
},
(err) => {
this.error = err.error;
this.saving = false;
}
);
} else {
this.cancel();
}
Expand Down
37 changes: 22 additions & 15 deletions client/src/app/torrent-status.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,36 @@ export class TorrentStatusPipe implements PipeTransform {
(m) => m.status === DownloadStatus.Unpacking
);

if (downloading.length > 0) {
const allBytesDownloaded = torrent.downloads.sum(
(m) => m.bytesDownloaded
);
const allBytesSize = torrent.downloads.sum((m) => m.bytesSize);
const allBytesDownloaded = torrent.downloads.sum(
(m) => m.bytesDownloaded
);
const allBytesSize = torrent.downloads.sum((m) => m.bytesSize);

if (allBytesSize > 0) {
const progress = ((allBytesDownloaded / allBytesSize) * 100).toFixed(
2
);
let progress = 0;
let allSpeeds = 0;

if (allBytesSize > 0) {
progress = (allBytesDownloaded / allBytesSize) * 100;
allSpeeds = downloading.sum((m) => m.speed) / downloading.length;
}

const allSpeeds =
downloading.sum((m) => m.speed) / downloading.length;
const speed = this.pipe.transform(allSpeeds, 'filesize');
console.log(allBytesDownloaded, allBytesSize, progress, allSpeeds);

return `Downloading (${progress || 0}% - ${speed}/s)`;
let speed: string | string[] = '0';
if (allSpeeds > 0) {
speed = this.pipe.transform(allSpeeds, 'filesize');
}

if (downloading.length > 0) {
if (allBytesSize > 0) {
return `Downloading (${progress.toFixed(2)}% - ${speed}/s)`;
}

return `Preparing download`;
}

if (unpacking.length > 0) {
return `Unpacking`;
return `Unpacking (${progress.toFixed(2)}% - ${speed}/s)`;
}

return 'Pending download';
Expand All @@ -58,7 +65,7 @@ export class TorrentStatusPipe implements PipeTransform {
const speed = this.pipe.transform(torrent.rdSpeed, 'filesize');
return `Torrent downloading (${torrent.rdProgress}% - ${speed}/s)`;
case TorrentStatus.WaitingForDownload:
return `Waiting to download`;
return `Ready to download, press the download icon to start`;
case TorrentStatus.DownloadQueued:
return `Download queued`;
case TorrentStatus.Downloading:
Expand Down
6 changes: 3 additions & 3 deletions client/src/app/torrent-table/torrent-table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<div class="table-container">
<table class="table is-fullwidth is-hoverable">
<colgroup>
<col />
<col />
<col />
<col style="width: 50%;" />
<col style="width: 15%;" />
<col style="width: 35%;" />
<col style="width: 50px;" />
<col style="width: 50px;" />
</colgroup>
Expand Down
19 changes: 16 additions & 3 deletions client/src/app/torrent.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,26 @@ export class TorrentService {
return this.http.get<Torrent>(`/Api/Torrents/${torrentId}`);
}

public uploadMagnet(magnetLink: string): Observable<void> {
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, { magnetLink });
public uploadMagnet(
magnetLink: string,
autoDownload: boolean,
autoDelete: boolean
): Observable<void> {
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
magnetLink,
autoDownload,
autoDelete,
});
}

public uploadFile(file: File): Observable<void> {
public uploadFile(
file: File,
autoDownload: boolean,
autoDelete: boolean
): Observable<void> {
const formData: FormData = new FormData();
formData.append('file', file);
formData.append('formData', JSON.stringify({ autoDownload, autoDelete }));
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData);
}

Expand Down
8 changes: 5 additions & 3 deletions server/RdtClient.Data/Data/TorrentData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface ITorrentData
Task<IList<Torrent>> Get();
Task<Torrent> GetById(Guid id);
Task<Torrent> GetByHash(String hash);
Task<Torrent> Add(String realDebridId, String hash);
Task<Torrent> Add(String realDebridId, String hash, Boolean autoDownload, Boolean autoDelete);
Task UpdateRdData(Torrent torrent);
Task UpdateStatus(Guid torrentId, TorrentStatus status);
Task UpdateCategory(Guid torrentId, String category);
Expand Down Expand Up @@ -80,14 +80,16 @@ public async Task<Torrent> GetByHash(String hash)
return results;
}

public async Task<Torrent> Add(String realDebridId, String hash)
public async Task<Torrent> Add(String realDebridId, String hash, Boolean autoDownload, Boolean autoDelete)
{
var torrent = new Torrent
{
TorrentId = Guid.NewGuid(),
RdId = realDebridId,
Hash = hash,
Status = TorrentStatus.RealDebrid
Status = TorrentStatus.RealDebrid,
AutoDownload = autoDownload,
AutoDelete = autoDelete
};

_dataContext.Torrents.Add(torrent);
Expand Down
33 changes: 33 additions & 0 deletions server/RdtClient.Service/Helpers/JsonModelBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace RdtClient.Service.Helpers
{
public class JsonModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}

var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);

var valueAsString = valueProviderResult.FirstValue;
var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType);
if (result != null)
{
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}

return Task.CompletedTask;
}
}
}
Loading

0 comments on commit f4ae266

Please sign in to comment.