Add running of external applications.
This commit is contained in:
parent
d4cb09e1a3
commit
6ca403f7f2
7 changed files with 152 additions and 0 deletions
|
|
@ -137,6 +137,40 @@
|
||||||
</div>
|
</div>
|
||||||
<p class="help">Maximum amount of downloads that get unpacked on your host at the same time.</p>
|
<p class="help">Maximum amount of downloads that get unpacked on your host at the same time.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Run external program on torrent completion</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" [(ngModel)]="settingRunOnTorrentCompleteFileName" />
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
Path to the executable to run when the torrent and all downloads are finished. No arguments should be passed here.
|
||||||
|
<br />
|
||||||
|
<strong>When running in Docker, this command will run on your docker instance!</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">External program arguments</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" [(ngModel)]="settingRunOnTorrentCompleteArguments" />
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
When the executable above is executed, use these parameters.
|
||||||
|
<br />
|
||||||
|
Supports the following parameters:
|
||||||
|
</p>
|
||||||
|
<ul class="help">
|
||||||
|
<li>%N: Torrent name</li>
|
||||||
|
<li>%L: Category</li>
|
||||||
|
<li>%F: Content path (same as root path for multifile torrent)</li>
|
||||||
|
<li>%R: Root path (first torrent subdirectory path)</li>
|
||||||
|
<li>%D: Save path</li>
|
||||||
|
<li>%C: Number of files</li>
|
||||||
|
<li>%Z: Torrent size (bytes)</li>
|
||||||
|
<li>%I: Info hash</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="activeTab === 1">
|
<div *ngIf="activeTab === 1">
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ export class SettingsComponent implements OnInit {
|
||||||
public settingTorrentRetryAttempts: number;
|
public settingTorrentRetryAttempts: number;
|
||||||
public settingDeleteOnError: number;
|
public settingDeleteOnError: number;
|
||||||
public settingTorrentLifetime: number;
|
public settingTorrentLifetime: number;
|
||||||
|
public settingRunOnTorrentCompleteFileName: string;
|
||||||
|
public settingRunOnTorrentCompleteArguments: string;
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService) {}
|
constructor(private settingsService: SettingsService) {}
|
||||||
|
|
||||||
|
|
@ -84,6 +86,8 @@ export class SettingsComponent implements OnInit {
|
||||||
this.settingTorrentRetryAttempts = parseInt(this.getSetting(results, 'TorrentRetryAttempts'), 10);
|
this.settingTorrentRetryAttempts = parseInt(this.getSetting(results, 'TorrentRetryAttempts'), 10);
|
||||||
this.settingDeleteOnError = parseInt(this.getSetting(results, 'DeleteOnError'), 10);
|
this.settingDeleteOnError = parseInt(this.getSetting(results, 'DeleteOnError'), 10);
|
||||||
this.settingTorrentLifetime = parseInt(this.getSetting(results, 'TorrentLifetime'), 10);
|
this.settingTorrentLifetime = parseInt(this.getSetting(results, 'TorrentLifetime'), 10);
|
||||||
|
this.settingRunOnTorrentCompleteFileName = this.getSetting(results, 'RunOnTorrentCompleteFileName');
|
||||||
|
this.settingRunOnTorrentCompleteArguments = this.getSetting(results, 'RunOnTorrentCompleteArguments');
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
this.error = err.error;
|
this.error = err.error;
|
||||||
|
|
@ -188,6 +192,14 @@ export class SettingsComponent implements OnInit {
|
||||||
settingId: 'TorrentLifetime',
|
settingId: 'TorrentLifetime',
|
||||||
value: (this.settingTorrentLifetime ?? 0).toString(),
|
value: (this.settingTorrentLifetime ?? 0).toString(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
settingId: 'RunOnTorrentCompleteFileName',
|
||||||
|
value: this.settingRunOnTorrentCompleteFileName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingId: 'RunOnTorrentCompleteArguments',
|
||||||
|
value: this.settingRunOnTorrentCompleteArguments,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
this.settingsService.update(settings).subscribe(
|
this.settingsService.update(settings).subscribe(
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,18 @@ namespace RdtClient.Data.Data
|
||||||
SettingId = "TorrentRetryAttempts",
|
SettingId = "TorrentRetryAttempts",
|
||||||
Type = "Int32",
|
Type = "Int32",
|
||||||
Value = "1"
|
Value = "1"
|
||||||
|
},
|
||||||
|
new Setting
|
||||||
|
{
|
||||||
|
SettingId = "RunOnTorrentCompleteFileName",
|
||||||
|
Type = "String",
|
||||||
|
Value = ""
|
||||||
|
},
|
||||||
|
new Setting
|
||||||
|
{
|
||||||
|
SettingId = "RunOnTorrentCompleteArguments",
|
||||||
|
Type = "String",
|
||||||
|
Value = ""
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,8 @@ namespace RdtClient.Data.Data
|
||||||
TorrentRetryAttempts = GetInt32("TorrentRetryAttempts"),
|
TorrentRetryAttempts = GetInt32("TorrentRetryAttempts"),
|
||||||
DeleteOnError = GetInt32("DeleteOnError"),
|
DeleteOnError = GetInt32("DeleteOnError"),
|
||||||
TorrentLifetime = GetInt32("TorrentLifetime"),
|
TorrentLifetime = GetInt32("TorrentLifetime"),
|
||||||
|
RunOnTorrentCompleteFileName = GetString("RunOnTorrentCompleteFileName"),
|
||||||
|
RunOnTorrentCompleteArguments = GetString("RunOnTorrentCompleteArguments")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,7 @@ namespace RdtClient.Data.Models.Internal
|
||||||
public Int32 TorrentRetryAttempts { get; set; }
|
public Int32 TorrentRetryAttempts { get; set; }
|
||||||
public Int32 DeleteOnError { get; set; }
|
public Int32 DeleteOnError { get; set; }
|
||||||
public Int32 TorrentLifetime { get; set; }
|
public Int32 TorrentLifetime { get; set; }
|
||||||
|
public String RunOnTorrentCompleteFileName { get; set; }
|
||||||
|
public String RunOnTorrentCompleteArguments { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -573,6 +573,8 @@ namespace RdtClient.Service.Services
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _torrents.RunTorrentComplete(torrent.TorrentId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -624,6 +627,91 @@ namespace RdtClient.Service.Services
|
||||||
await _torrentData.Update(torrent);
|
await _torrentData.Update(torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task RunTorrentComplete(Guid torrentId)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrWhiteSpace(Settings.Get.RunOnTorrentCompleteFileName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var torrent = await _torrentData.GetById(torrentId);
|
||||||
|
var downloads = await _downloads.GetForTorrent(torrentId);
|
||||||
|
|
||||||
|
var fileName = Settings.Get.RunOnTorrentCompleteFileName;
|
||||||
|
var arguments = Settings.Get.RunOnTorrentCompleteArguments ?? "";
|
||||||
|
|
||||||
|
Log($"Parsing external program {fileName} with arguments {arguments}", torrent);
|
||||||
|
|
||||||
|
var downloadPath = DownloadPath(torrent);
|
||||||
|
var torrentPath = Path.Combine(downloadPath, torrent.RdName);
|
||||||
|
|
||||||
|
arguments = arguments.Replace("%N", $"\"{torrent.RdName}\"");
|
||||||
|
arguments = arguments.Replace("%L", $"\"{torrent.Category}\"");
|
||||||
|
arguments = arguments.Replace("%F", $"\"{torrentPath}\"");
|
||||||
|
arguments = arguments.Replace("%R", $"\"{downloadPath}\"");
|
||||||
|
arguments = arguments.Replace("%D", $"\"{torrentPath}\"");
|
||||||
|
arguments = arguments.Replace("%C", downloads.Count.ToString(CultureInfo.InvariantCulture).Replace(",", "").Replace(".", ""));
|
||||||
|
arguments = arguments.Replace("%Z", torrent.RdSize.ToString(CultureInfo.InvariantCulture).Replace(",", "").Replace(".", ""));
|
||||||
|
arguments = arguments.Replace("%I", torrent.Hash);
|
||||||
|
|
||||||
|
Log($"Executing external program {fileName} with arguments {arguments}", torrent);
|
||||||
|
|
||||||
|
var errorSb = new StringBuilder();
|
||||||
|
var outputSb = new StringBuilder();
|
||||||
|
|
||||||
|
using var process = new Process();
|
||||||
|
|
||||||
|
process.StartInfo.FileName = fileName;
|
||||||
|
process.StartInfo.Arguments = arguments;
|
||||||
|
process.StartInfo.CreateNoWindow = true;
|
||||||
|
process.StartInfo.UseShellExecute = false;
|
||||||
|
process.StartInfo.RedirectStandardOutput = true;
|
||||||
|
process.StartInfo.RedirectStandardError = true;
|
||||||
|
|
||||||
|
process.OutputDataReceived += (_, data) =>
|
||||||
|
{
|
||||||
|
if (data.Data == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputSb.AppendLine(data.Data.Trim());
|
||||||
|
};
|
||||||
|
process.ErrorDataReceived += (_, data) =>
|
||||||
|
{
|
||||||
|
if (data.Data == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorSb.AppendLine(data.Data.Trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
process.BeginErrorReadLine();
|
||||||
|
|
||||||
|
var exited = process.WaitForExit(60000 * 10);
|
||||||
|
|
||||||
|
var errors = errorSb.ToString();
|
||||||
|
var output = outputSb.ToString();
|
||||||
|
|
||||||
|
if (errors.Length > 0)
|
||||||
|
{
|
||||||
|
Log($"External application exited with errors: {errors}", torrent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.Length > 0)
|
||||||
|
{
|
||||||
|
Log($"External application exited with output: {output}", torrent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exited)
|
||||||
|
{
|
||||||
|
Log("External application after a 60 second timeout", torrent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task UpdateTorrentClientData(Torrent torrent, TorrentClientTorrent torrentClientTorrent = null)
|
private async Task UpdateTorrentClientData(Torrent torrent, TorrentClientTorrent torrentClientTorrent = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue