Add running of external applications.

This commit is contained in:
Roger Far 2022-03-19 15:12:37 -06:00
parent d4cb09e1a3
commit 6ca403f7f2
7 changed files with 152 additions and 0 deletions

View file

@ -137,6 +137,40 @@
</div>
<p class="help">Maximum amount of downloads that get unpacked on your host at the same time.</p>
</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 *ngIf="activeTab === 1">

View file

@ -48,6 +48,8 @@ export class SettingsComponent implements OnInit {
public settingTorrentRetryAttempts: number;
public settingDeleteOnError: number;
public settingTorrentLifetime: number;
public settingRunOnTorrentCompleteFileName: string;
public settingRunOnTorrentCompleteArguments: string;
constructor(private settingsService: SettingsService) {}
@ -84,6 +86,8 @@ export class SettingsComponent implements OnInit {
this.settingTorrentRetryAttempts = parseInt(this.getSetting(results, 'TorrentRetryAttempts'), 10);
this.settingDeleteOnError = parseInt(this.getSetting(results, 'DeleteOnError'), 10);
this.settingTorrentLifetime = parseInt(this.getSetting(results, 'TorrentLifetime'), 10);
this.settingRunOnTorrentCompleteFileName = this.getSetting(results, 'RunOnTorrentCompleteFileName');
this.settingRunOnTorrentCompleteArguments = this.getSetting(results, 'RunOnTorrentCompleteArguments');
},
(err) => {
this.error = err.error;
@ -188,6 +192,14 @@ export class SettingsComponent implements OnInit {
settingId: 'TorrentLifetime',
value: (this.settingTorrentLifetime ?? 0).toString(),
},
{
settingId: 'RunOnTorrentCompleteFileName',
value: this.settingRunOnTorrentCompleteFileName,
},
{
settingId: 'RunOnTorrentCompleteArguments',
value: this.settingRunOnTorrentCompleteArguments,
},
];
this.settingsService.update(settings).subscribe(

View file

@ -195,6 +195,18 @@ namespace RdtClient.Data.Data
SettingId = "TorrentRetryAttempts",
Type = "Int32",
Value = "1"
},
new Setting
{
SettingId = "RunOnTorrentCompleteFileName",
Type = "String",
Value = ""
},
new Setting
{
SettingId = "RunOnTorrentCompleteArguments",
Type = "String",
Value = ""
}
};

View file

@ -72,6 +72,8 @@ namespace RdtClient.Data.Data
TorrentRetryAttempts = GetInt32("TorrentRetryAttempts"),
DeleteOnError = GetInt32("DeleteOnError"),
TorrentLifetime = GetInt32("TorrentLifetime"),
RunOnTorrentCompleteFileName = GetString("RunOnTorrentCompleteFileName"),
RunOnTorrentCompleteArguments = GetString("RunOnTorrentCompleteArguments")
};
}

View file

@ -28,5 +28,7 @@ namespace RdtClient.Data.Models.Internal
public Int32 TorrentRetryAttempts { get; set; }
public Int32 DeleteOnError { get; set; }
public Int32 TorrentLifetime { get; set; }
public String RunOnTorrentCompleteFileName { get; set; }
public String RunOnTorrentCompleteArguments { get; set; }
}
}

View file

@ -573,6 +573,8 @@ namespace RdtClient.Service.Services
break;
}
await _torrents.RunTorrentComplete(torrent.TorrentId);
}
else
{

View file

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@ -624,6 +627,91 @@ namespace RdtClient.Service.Services
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)
{
try