Added the ability to run a user defined update script #1460

pull/1614/head
Jamie.Rees 7 years ago
parent ef8b73972e
commit b4d01386a6

@ -38,6 +38,7 @@ using Ombi.Api.Service;
using Ombi.Api.Slack;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Senders;
using Ombi.Helpers;
using Ombi.Schedule.Jobs.Couchpotato;
using Ombi.Schedule.Jobs.Emby;
using Ombi.Schedule.Jobs.Ombi;
@ -45,6 +46,7 @@ using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Sonarr;
using Ombi.Store.Entities;
using Ombi.Store.Repository.Requests;
using Ombi.Updater;
using PlexContentCacher = Ombi.Schedule.Jobs.Plex.PlexContentCacher;
namespace Ombi.DependencyInjection
@ -152,6 +154,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IEmbyUserImporter, EmbyUserImporter>();
services.AddTransient<IWelcomeEmail, WelcomeEmail>();
services.AddTransient<ICouchPotatoCacher, CouchPotatoCacher>();
services.AddTransient<IProcessProvider, ProcessProvider>();
}
}
}

@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Diagnostics;
using Ombi.Helpers;
namespace Ombi.Updater
{
public interface IProcessProvider
{
bool Exists(int processId);
bool Exists(string processName);
List<ProcessInfo> FindProcessByName(string name);
ProcessInfo GetCurrentProcess();
int GetCurrentProcessId();
ProcessInfo GetProcessById(int id);
void Kill(int processId);
void KillAll(string processName);
void SetPriority(int processId, ProcessPriorityClass priority);
Process Start(string path, string args = null);
void WaitForExit(Process process);
}
}

@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Extensions.Logging;
using Ombi.Updater;
namespace Ombi.Helpers
{
public class ProcessProvider : IProcessProvider
{
public ProcessProvider(ILogger<ProcessProvider> log)
{
_log = log;
}
private readonly ILogger<ProcessProvider> _log;
public int GetCurrentProcessId()
{
return Process.GetCurrentProcess().Id;
}
public ProcessInfo GetCurrentProcess()
{
return ConvertToProcessInfo(Process.GetCurrentProcess());
}
public bool Exists(int processId)
{
return GetProcessById(processId) != null;
}
public bool Exists(string processName)
{
return GetProcessesByName(processName).Any();
}
public ProcessInfo GetProcessById(int id)
{
_log.LogInformation("Finding process with Id:{0}", id);
var processInfo = ConvertToProcessInfo(Process.GetProcesses().FirstOrDefault(p => p.Id == id));
if (processInfo == null)
{
_log.LogInformation("Unable to find process with ID {0}", id);
}
else
{
_log.LogInformation("Found process {0}", processInfo.ToString());
}
return processInfo;
}
public List<ProcessInfo> FindProcessByName(string name)
{
return GetProcessesByName(name).Select(ConvertToProcessInfo).Where(c => c != null).ToList();
}
public void WaitForExit(Process process)
{
_log.LogInformation("Waiting for process {0} to exit.", process.ProcessName);
process.WaitForExit();
}
public void SetPriority(int processId, ProcessPriorityClass priority)
{
var process = Process.GetProcessById(processId);
_log.LogInformation("Updating [{0}] process priority from {1} to {2}",
process.ProcessName,
process.PriorityClass,
priority);
process.PriorityClass = priority;
}
public void Kill(int processId)
{
var process = Process.GetProcesses().FirstOrDefault(p => p.Id == processId);
if (process == null)
{
_log.LogInformation("Cannot find process with id: {0}", processId);
return;
}
process.Refresh();
if (process.Id != Process.GetCurrentProcess().Id && process.HasExited)
{
_log.LogInformation("Process has already exited");
return;
}
_log.LogInformation("[{0}]: Killing process", process.Id);
process.Kill();
_log.LogInformation("[{0}]: Waiting for exit", process.Id);
process.WaitForExit();
_log.LogInformation("[{0}]: Process terminated successfully", process.Id);
}
public void KillAll(string processName)
{
var processes = GetProcessesByName(processName);
_log.LogInformation("Found {0} processes to kill", processes.Count);
foreach (var processInfo in processes)
{
if (processInfo.Id == Process.GetCurrentProcess().Id)
{
_log.LogInformation("Tried killing own process, skipping: {0} [{1}]", processInfo.Id, processInfo.ProcessName);
continue;
}
_log.LogInformation("Killing process: {0} [{1}]", processInfo.Id, processInfo.ProcessName);
Kill(processInfo.Id);
}
}
private ProcessInfo ConvertToProcessInfo(Process process)
{
if (process == null) return null;
process.Refresh();
ProcessInfo processInfo = null;
try
{
if (process.Id <= 0) return null;
processInfo = new ProcessInfo
{
Id = process.Id,
Name = process.ProcessName,
StartPath = GetExeFileName(process)
};
if (process.Id != Process.GetCurrentProcess().Id && process.HasExited)
{
processInfo = null;
}
}
catch (Exception e)
{
_log.LogInformation(e.Message);
}
return processInfo;
}
public Process Start(string path, string args = null)
{
var startInfo = new ProcessStartInfo(path, args)
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true
};
_log.LogDebug("Starting {0} {1}", path, args);
var process = new Process
{
StartInfo = startInfo
};
process.OutputDataReceived += (sender, eventArgs) =>
{
if (string.IsNullOrWhiteSpace(eventArgs.Data)) return;
_log.LogDebug(eventArgs.Data);
};
process.ErrorDataReceived += (sender, eventArgs) =>
{
if (string.IsNullOrWhiteSpace(eventArgs.Data)) return;
_log.LogDebug(eventArgs.Data);
};
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
return process;
}
private static string GetExeFileName(Process process)
{
return process.MainModule.FileName;
}
private List<Process> GetProcessesByName(string name)
{
var processes = Process.GetProcessesByName(name).ToList();
_log.LogInformation("Found {0} processes with the name: {1}", processes.Count, name);
try
{
foreach (var process in processes)
{
_log.LogInformation(" - [{0}] {1}", process.Id, process.ProcessName);
}
}
catch
{
// Don't crash on gettings some log data.
}
return processes;
}
}
public class ProcessInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string StartPath { get; set; }
public override string ToString()
{
return string.Format("{0}:{1} [{2}]", Id, Name ?? "Unknown", StartPath ?? "Unknown");
}
}
}

@ -19,6 +19,7 @@ using Ombi.Api.Service.Models;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Updater;
using SharpCompress.Readers;
using SharpCompress.Readers.Tar;
@ -27,16 +28,18 @@ namespace Ombi.Schedule.Jobs.Ombi
public class OmbiAutomaticUpdater : IOmbiAutomaticUpdater
{
public OmbiAutomaticUpdater(ILogger<OmbiAutomaticUpdater> log, IOmbiService service,
ISettingsService<UpdateSettings> s)
ISettingsService<UpdateSettings> s, IProcessProvider proc)
{
Logger = log;
OmbiService = service;
Settings = s;
_processProvider = proc;
}
private ILogger<OmbiAutomaticUpdater> Logger { get; }
private IOmbiService OmbiService { get; }
private ISettingsService<UpdateSettings> Settings { get; }
private readonly IProcessProvider _processProvider;
private static PerformContext Ctx { get; set; }
public string[] GetVersion()
@ -126,6 +129,22 @@ namespace Ombi.Schedule.Jobs.Ombi
Ctx.WriteLine("Found the download! {0}", download.Name);
Ctx.WriteLine("URL {0}", download.Url);
Ctx.WriteLine("Clearing out Temp Path");
var tempPath = Path.Combine(currentLocation, "TempUpdate");
if (Directory.Exists(tempPath))
{
Directory.Delete(tempPath, true);
}
// Temp Path
Directory.CreateDirectory(tempPath);
if (settings.UseScript)
{
RunScript(settings, download.Url);
return;
}
// Download it
Logger.LogInformation(LoggingEvents.Updater, "Downloading the file {0} from {1}", download.Name, download.Url);
var extension = download.Name.Split('.').Last();
@ -149,12 +168,7 @@ namespace Ombi.Schedule.Jobs.Ombi
Logger.LogError(LoggingEvents.Updater, e, "Error when downloading the zip");
throw;
}
Ctx.WriteLine("Clearing out Temp Path");
var tempPath = Path.Combine(currentLocation, "TempUpdate");
if (Directory.Exists(tempPath))
{
Directory.Delete(tempPath, true);
}
// Extract it
Ctx.WriteLine("Extracting ZIP");
Extract(zipDir, tempPath);
@ -199,14 +213,34 @@ namespace Ombi.Schedule.Jobs.Ombi
}
}
private void RunScript(UpdateSettings settings, string downloadUrl)
{
var scriptToRun = settings.ScriptLocation;
if (scriptToRun.IsNullOrEmpty())
{
Logger.LogError("Use Script is enabled but there is no script to run");
return;
}
if (!File.Exists(scriptToRun))
{
Logger.LogError("Cannot find the file {0}", scriptToRun);
return;
}
var ombiProcess = _processProvider.FindProcessByName(settings.ProcessName).FirstOrDefault();
var currentInstallLocation = Assembly.GetEntryAssembly().Location;
_processProvider.Start(scriptToRun, string.Join(" ", downloadUrl, ombiProcess.Id, currentInstallLocation));
Logger.LogInformation("Script started");
}
private void Extract(string zipDir, string tempPath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
using (var files = ZipFile.OpenRead(zipDir))
{
// Temp Path
Directory.CreateDirectory(tempPath);
foreach (var entry in files.Entries)
{
if (entry.FullName.Contains("/"))

@ -6,5 +6,8 @@
public string Username { get; set; }
public string Password { get; set; }
public string ProcessName { get; set; }
public bool UseScript { get; set; }
public string ScriptLocation { get; set; }
}
}

@ -82,7 +82,7 @@ namespace Ombi.Tests
_userManager = _serviceProvider.GetRequiredService<OmbiUserManager>();
Controller = new IdentityController(_userManager, _mapper.Object, _serviceProvider.GetService<RoleManager<IdentityRole>>(), _emailProvider.Object,
_emailSettings.Object, _customizationSettings.Object,_welcomeEmail.Object);
_emailSettings.Object, _customizationSettings.Object,_welcomeEmail.Object, null, null, null);
}
private OmbiUserManager _userManager;

@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Diagnostics;
namespace Ombi.Updater
{
public interface IProcessProvider
{
bool Exists(int processId);
bool Exists(string processName);
List<ProcessInfo> FindProcessByName(string name);
ProcessInfo GetCurrentProcess();
int GetCurrentProcessId();
ProcessInfo GetProcessById(int id);
void Kill(int processId);
void KillAll(string processName);
void SetPriority(int processId, ProcessPriorityClass priority);
void WaitForExit(Process process);
}
}

@ -5,9 +5,12 @@ using System.Linq;
namespace Ombi.Updater
{
public class ProcessProvider
public class ProcessProvider : IProcessProvider
{
public const string OmbiProcessName = "Ombi";
public ProcessProvider()
{
}
public int GetCurrentProcessId()
{

@ -57,7 +57,8 @@ namespace Ombi.Updater
//// Add services
serviceCollection.AddTransient<IInstaller, Installer>();
}
serviceCollection.AddTransient<IProcessProvider, ProcessProvider>();
}
private static StartupOptions CheckArgs(string[] args)
{

@ -20,6 +20,8 @@ export interface IUpdateSettings extends ISettings {
username: string;
password: string;
processName: string;
useScript: boolean;
scriptLocation: string;
}
export interface IEmbySettings extends ISettings {

@ -1,11 +1,12 @@

<settings-menu></settings-menu>
<wiki [url]="'https://github.com/tidusjar/Ombi/wiki/Update-Settings'"></wiki>
<div *ngIf="form">
<fieldset>
<legend>Update Settings</legend>
<div class="form-group" style="float: right">
<div *ngIf="updateAvailable">
<button (click)="update()" class="btn btn-success-outline">Update</button>
<button (click)="update()" [disabled]="!enableUpdateButton" class="btn btn-success-outline">Update</button>
</div>
<div *ngIf="!updateAvailable">
<button (click)="checkForUpdate()" class="btn btn-primary-outline">Check For Update</button>
@ -20,23 +21,28 @@
</div>
</div>
<small>If you are getting any permissions issues, you can specify a user for the update process to run under.</small>
<div class="form-group">
<label for="username" class="control-label">Username</label>
<input type="text" class="form-control form-control-custom " id="username" name="username" formControlName="username">
<div class="checkbox">
<input type="checkbox" id="useScript" formControlName="useScript">
<label for="useScript">Use your own updater script</label>
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" class="form-control form-control-custom" formControlName="password">
<div [hidden]="!useScript">
<small>For information how to use this, please press the wiki button at the top of the page</small>
<div class="form-group">
<label for="scriptLocation" class="control-label">Script Path</label>
<input type="text" class="form-control form-control-custom " id="scriptLocation" name="scriptLocation" formControlName="scriptLocation">
</div>
</div>
<small>By default the process name is Ombi, but this could be different for your system. We need to know the process name so we can kill that process to update the files.</small>
<div class="form-group">
<label for="processName">Ombi Process Name</label>
<input type="text" id="processName" class="form-control form-control-custom" placeholder="Ombi" formControlName="processName">
<div [hidden]="useScript">
<small >By default the process name is Ombi, but this could be different for your system. We need to know the process name so we can kill that process to update the files.</small>
<div class="form-group">
<label for="processName">Ombi Process Name</label>
<input type="text" id="processName" class="form-control form-control-custom" placeholder="Ombi" formControlName="processName">
</div>
</div>
<div class="form-group">
@ -45,6 +51,19 @@
</div>
</div>
</div>
<div class="col-md-6" [hidden]="useScript">
<small>If you are getting any permissions issues, you can specify a user for the update process to run under (Only supported on Windows).</small>
<div class="form-group">
<label for="username" class="control-label">Username</label>
<input type="text" class="form-control form-control-custom " id="username" name="username" formControlName="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" class="form-control form-control-custom" formControlName="password">
</div>
</div>
</form>
</fieldset>
</div>

@ -11,6 +11,12 @@ export class UpdateComponent implements OnInit {
public form: FormGroup;
public updateAvailable = false;
public enableUpdateButton = false;
public get useScript() {
const control = this.form.get("useScript");
console.log(control);
return control!.value!;
}
constructor(private settingsService: SettingsService,
private notificationService: NotificationService,
@ -25,7 +31,10 @@ export class UpdateComponent implements OnInit {
username: [x.username],
password: [x.password],
processName: [x.processName],
useScript: [x.useScript],
scriptLocation: [x.scriptLocation],
});
this.enableUpdateButton = x.autoUpdateEnabled;
});
}
@ -50,6 +59,7 @@ export class UpdateComponent implements OnInit {
this.notificationService.error("Validation", "Please check your entered values");
return;
}
this.enableUpdateButton = form.value.autoUpdateEnabled;
this.settingsService.saveUpdateSettings(form.value)
.subscribe(x => {
if (x) {

Loading…
Cancel
Save