Merge branch 'hp-timeout' of https://github.com/smcpeck/Ombi into hp-timeout

This commit is contained in:
smcpeck 2017-02-27 20:04:32 -06:00
commit f931750f98
40 changed files with 1545 additions and 366 deletions

View file

@ -0,0 +1,54 @@
using System;
using System.IO;
namespace Ombi.Common.EnvironmentInfo
{
public class OsInfo
{
public static Os Os { get; }
public static bool IsNotWindows => !IsWindows;
public static bool IsLinux => Os == Os.Linux;
public static bool IsOsx => Os == Os.Osx;
public static bool IsWindows => Os == Os.Windows;
static OsInfo()
{
var platform = Environment.OSVersion.Platform;
switch (platform)
{
case PlatformID.Win32NT:
{
Os = Os.Windows;
break;
}
case PlatformID.MacOSX:
case PlatformID.Unix:
{
// Sometimes Mac OS reports itself as Unix
if (Directory.Exists("/System/Library/CoreServices/") &&
(File.Exists("/System/Library/CoreServices/SystemVersion.plist") ||
File.Exists("/System/Library/CoreServices/ServerVersion.plist"))
)
{
Os = Os.Osx;
}
else
{
Os = Os.Linux;
}
break;
}
}
}
}
public enum Os
{
Windows,
Linux,
Osx
}
}

View file

@ -0,0 +1,42 @@
using System;
namespace Ombi.Common.EnvironmentInfo
{
public enum PlatformType
{
DotNet = 0,
Mono = 1
}
public interface IPlatformInfo
{
Version Version { get; }
}
public abstract class PlatformInfo : IPlatformInfo
{
static PlatformInfo()
{
Platform = Type.GetType("Mono.Runtime") != null ? PlatformType.Mono : PlatformType.DotNet;
}
public static PlatformType Platform { get; }
public static bool IsMono => Platform == PlatformType.Mono;
public static bool IsDotNet => Platform == PlatformType.DotNet;
public static string PlatformName
{
get
{
if (IsDotNet)
{
return ".NET";
}
return "Mono";
}
}
public abstract Version Version { get; }
}
}

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BFD45569-90CF-47CA-B575-C7B0FF97F67B}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Ombi.Common</RootNamespace>
<AssemblyName>Ombi.Common</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="EnvironmentInfo\OsInfo.cs" />
<Compile Include="EnvironmentInfo\PlatformInfo.cs" />
<Compile Include="Processes\ProcessInfo.cs" />
<Compile Include="Processes\ProcessOutput.cs" />
<Compile Include="Processes\ProcessProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceProvider.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ombi.Common.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");
}
}
}

View file

@ -0,0 +1,59 @@

using System;
using System.Collections.Generic;
using System.Linq;
namespace Ombi.Common.Processes
{
public class ProcessOutput
{
public int ExitCode { get; set; }
public List<ProcessOutputLine> Lines { get; set; }
public ProcessOutput()
{
Lines = new List<ProcessOutputLine>();
}
public List<ProcessOutputLine> Standard
{
get
{
return Lines.Where(c => c.Level == ProcessOutputLevel.Standard).ToList();
}
}
public List<ProcessOutputLine> Error
{
get
{
return Lines.Where(c => c.Level == ProcessOutputLevel.Error).ToList();
}
}
}
public class ProcessOutputLine
{
public ProcessOutputLevel Level { get; set; }
public string Content { get; set; }
public DateTime Time { get; set; }
public ProcessOutputLine(ProcessOutputLevel level, string content)
{
Level = level;
Content = content;
Time = DateTime.UtcNow;
}
public override string ToString()
{
return string.Format("{0} - {1} - {2}", Time, Level, Content);
}
}
public enum ProcessOutputLevel
{
Standard = 0,
Error = 1
}
}

View file

@ -0,0 +1,343 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NLog;
using Ombi.Common.EnvironmentInfo;
namespace Ombi.Common.Processes
{
public interface IProcessProvider
{
int GetCurrentProcessId();
ProcessInfo GetCurrentProcess();
ProcessInfo GetProcessById(int id);
List<ProcessInfo> FindProcessByName(string name);
void OpenDefaultBrowser(string url);
void WaitForExit(System.Diagnostics.Process process);
void SetPriority(int processId, ProcessPriorityClass priority);
void KillAll(string processName);
void Kill(int processId);
bool Exists(int processId);
bool Exists(string processName);
ProcessPriorityClass GetCurrentProcessPriority();
System.Diagnostics.Process Start(string path, string args = null, StringDictionary environmentVariables = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null);
System.Diagnostics.Process SpawnNewProcess(string path, string args = null, StringDictionary environmentVariables = null);
ProcessOutput StartAndCapture(string path, string args = null, StringDictionary environmentVariables = null);
}
public class ProcessProvider : IProcessProvider
{
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
public const string OmbiProcessName = "Ombi";
//public ProcessProvider(Logger logger)
//{
// _logger = logger;
//}
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 ProcessPriorityClass GetCurrentProcessPriority()
{
return Process.GetCurrentProcess().PriorityClass;
}
public ProcessInfo GetProcessById(int id)
{
_logger.Debug("Finding process with Id:{0}", id);
var processInfo = ConvertToProcessInfo(Process.GetProcesses().FirstOrDefault(p => p.Id == id));
if (processInfo == null)
{
_logger.Warn("Unable to find process with ID {0}", id);
}
else
{
_logger.Debug("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 OpenDefaultBrowser(string url)
{
_logger.Info("Opening URL [{0}]", url);
var process = new Process
{
StartInfo = new ProcessStartInfo(url)
{
UseShellExecute = true
}
};
process.Start();
}
public Process Start(string path, string args = null, StringDictionary environmentVariables = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null)
{
if (PlatformInfo.IsMono && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
{
args = GetMonoArgs(path, args);
path = "mono";
}
var logger = LogManager.GetLogger(new FileInfo(path).Name);
var startInfo = new ProcessStartInfo(path, args)
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true
};
if (environmentVariables != null)
{
foreach (DictionaryEntry environmentVariable in environmentVariables)
{
startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
}
}
logger.Debug("Starting {0} {1}", path, args);
var process = new Process
{
StartInfo = startInfo
};
process.OutputDataReceived += (sender, eventArgs) =>
{
if (string.IsNullOrWhiteSpace(eventArgs.Data)) return;
logger.Debug(eventArgs.Data);
onOutputDataReceived?.Invoke(eventArgs.Data);
};
process.ErrorDataReceived += (sender, eventArgs) =>
{
if (string.IsNullOrWhiteSpace(eventArgs.Data)) return;
logger.Error(eventArgs.Data);
onErrorDataReceived?.Invoke(eventArgs.Data);
};
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
return process;
}
public Process SpawnNewProcess(string path, string args = null, StringDictionary environmentVariables = null)
{
if (PlatformInfo.IsMono && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
{
args = GetMonoArgs(path, args);
path = "mono";
}
_logger.Debug("Starting {0} {1}", path, args);
var startInfo = new ProcessStartInfo(path, args);
var process = new Process
{
StartInfo = startInfo
};
process.Start();
return process;
}
public ProcessOutput StartAndCapture(string path, string args = null, StringDictionary environmentVariables = null)
{
var output = new ProcessOutput();
var process = Start(path, args, environmentVariables, s => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Standard, s)),
error => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Error, error)));
process.WaitForExit();
output.ExitCode = process.ExitCode;
return output;
}
public void WaitForExit(Process process)
{
_logger.Debug("Waiting for process {0} to exit.", process.ProcessName);
process.WaitForExit();
}
public void SetPriority(int processId, ProcessPriorityClass priority)
{
var process = Process.GetProcessById(processId);
_logger.Info("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)
{
_logger.Warn("Cannot find process with id: {0}", processId);
return;
}
process.Refresh();
if (process.Id != Process.GetCurrentProcess().Id && process.HasExited)
{
_logger.Debug("Process has already exited");
return;
}
_logger.Info("[{0}]: Killing process", process.Id);
process.Kill();
_logger.Info("[{0}]: Waiting for exit", process.Id);
process.WaitForExit();
_logger.Info("[{0}]: Process terminated successfully", process.Id);
}
public void KillAll(string processName)
{
var processes = GetProcessesByName(processName);
_logger.Debug("Found {0} processes to kill", processes.Count);
foreach (var processInfo in processes)
{
if (processInfo.Id == Process.GetCurrentProcess().Id)
{
_logger.Debug("Tried killing own process, skipping: {0} [{1}]", processInfo.Id, processInfo.ProcessName);
continue;
}
_logger.Debug("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 (Win32Exception e)
{
_logger.Warn(e, "Couldn't get process info for " + process.ProcessName);
}
return processInfo;
}
private static string GetExeFileName(Process process)
{
if (process.MainModule.FileName != "mono.exe")
{
return process.MainModule.FileName;
}
return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
}
private List<System.Diagnostics.Process> GetProcessesByName(string name)
{
//TODO: move this to an OS specific class
var monoProcesses = Process.GetProcessesByName("mono")
.Union(Process.GetProcessesByName("mono-sgen"))
.Where(process =>
process.Modules.Cast<ProcessModule>()
.Any(module =>
module.ModuleName.ToLower() == name.ToLower() + ".exe"));
var processes = Process.GetProcessesByName(name)
.Union(monoProcesses).ToList();
_logger.Debug("Found {0} processes with the name: {1}", processes.Count, name);
try
{
foreach (var process in processes)
{
_logger.Debug(" - [{0}] {1}", process.Id, process.ProcessName);
}
}
catch
{
// Don't crash on gettings some log data.
}
return processes;
}
private string GetMonoArgs(string path, string args)
{
return string.Format("--debug {0} {1}", path, args);
}
}
}

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Ombi.Common")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Ombi.Common")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("bfd45569-90cf-47ca-b575-c7b0ff97f67b")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,203 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: ServiceProvider.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Specialized;
using System.Configuration.Install;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using NLog;
using Ombi.Common.Processes;
namespace Ombi.Common
{
public interface IServiceProvider
{
bool ServiceExist(string name);
bool IsServiceRunning(string name);
void Install(string serviceName);
void Run(ServiceBase service);
ServiceController GetService(string serviceName);
void Stop(string serviceName);
void Start(string serviceName);
ServiceControllerStatus GetStatus(string serviceName);
void Restart(string serviceName);
}
public class ServiceProvider : IServiceProvider
{
public const string OmbiServiceName = "Ombi";
private readonly IProcessProvider _processProvider;
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
public ServiceProvider(IProcessProvider processProvider)
{
_processProvider = processProvider;
}
public virtual bool ServiceExist(string name)
{
_logger.Debug("Checking if service {0} exists.", name);
return
ServiceController.GetServices().Any(
s => string.Equals(s.ServiceName, name, StringComparison.InvariantCultureIgnoreCase));
}
public virtual bool IsServiceRunning(string name)
{
_logger.Debug("Checking if '{0}' service is running", name);
var service = ServiceController.GetServices()
.SingleOrDefault(s => string.Equals(s.ServiceName, name, StringComparison.InvariantCultureIgnoreCase));
return service != null && (
service.Status != ServiceControllerStatus.Stopped ||
service.Status == ServiceControllerStatus.StopPending ||
service.Status == ServiceControllerStatus.Paused ||
service.Status == ServiceControllerStatus.PausePending);
}
public virtual void Install(string serviceName)
{
_logger.Info("Installing service '{0}'", serviceName);
var installer = new ServiceProcessInstaller
{
Account = ServiceAccount.LocalSystem
};
var serviceInstaller = new ServiceInstaller();
string[] cmdline = { @"/assemblypath=" + Process.GetCurrentProcess().MainModule.FileName };
var context = new InstallContext("service_install.log", cmdline);
serviceInstaller.Context = context;
serviceInstaller.DisplayName = serviceName;
serviceInstaller.ServiceName = serviceName;
serviceInstaller.Description = "Ombi Application Server";
serviceInstaller.StartType = ServiceStartMode.Automatic;
serviceInstaller.ServicesDependedOn = new[] { "EventLog", "Tcpip", "http" };
serviceInstaller.Parent = installer;
serviceInstaller.Install(new ListDictionary());
_logger.Info("Service Has installed successfully.");
}
public virtual void Run(ServiceBase service)
{
ServiceBase.Run(service);
}
public virtual ServiceController GetService(string serviceName)
{
return ServiceController.GetServices().FirstOrDefault(c => string.Equals(c.ServiceName, serviceName, StringComparison.InvariantCultureIgnoreCase));
}
public virtual void Stop(string serviceName)
{
_logger.Info("Stopping {0} Service...", serviceName);
var service = GetService(serviceName);
if (service == null)
{
_logger.Warn("Unable to stop {0}. no service with that name exists.", serviceName);
return;
}
_logger.Info("Service is currently {0}", service.Status);
if (service.Status != ServiceControllerStatus.Stopped)
{
service.Stop();
service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(60));
service.Refresh();
if (service.Status == ServiceControllerStatus.Stopped)
{
_logger.Info("{0} has stopped successfully.", serviceName);
}
else
{
_logger.Error("Service stop request has timed out. {0}", service.Status);
}
}
else
{
_logger.Warn("Service {0} is already in stopped state.", service.ServiceName);
}
}
public ServiceControllerStatus GetStatus(string serviceName)
{
return GetService(serviceName).Status;
}
public void Start(string serviceName)
{
_logger.Info("Starting {0} Service...", serviceName);
var service = GetService(serviceName);
if (service == null)
{
_logger.Warn("Unable to start '{0}' no service with that name exists.", serviceName);
return;
}
if (service.Status != ServiceControllerStatus.Paused && service.Status != ServiceControllerStatus.Stopped)
{
_logger.Warn("Service is in a state that can't be started. Current status: {0}", service.Status);
}
service.Start();
service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(60));
service.Refresh();
if (service.Status == ServiceControllerStatus.Running)
{
_logger.Info("{0} has started successfully.", serviceName);
}
else
{
_logger.Error("Service start request has timed out. {0}", service.Status);
}
}
public void Restart(string serviceName)
{
var args = string.Format("/C net.exe stop \"{0}\" && net.exe start \"{0}\"", serviceName);
_processProvider.Start("cmd.exe", args);
}
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NLog" version="4.3.6" targetFramework="net452" />
</packages>

View file

@ -27,26 +27,37 @@
#endregion #endregion
using System;
using System.Data; using System.Data;
using NLog; using NLog;
using Ombi.Core.SettingModels; using Ombi.Core.SettingModels;
using Ombi.Store; using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Quartz.Collection;
namespace Ombi.Core.Migration.Migrations namespace Ombi.Core.Migration.Migrations
{ {
[Migration(22000, "v2.20.0.0")] [Migration(22000, "v2.20.0.0")]
public class Version2200 : BaseMigration, IMigration public class Version2200 : BaseMigration, IMigration
{ {
public Version2200(ISettingsService<CustomizationSettings> custom, ISettingsService<PlexSettings> ps) public Version2200(ISettingsService<CustomizationSettings> custom, ISettingsService<PlexSettings> ps, IRepository<RecentlyAddedLog> log,
IRepository<PlexContent> content, IRepository<PlexEpisodes> plexEp)
{ {
Customization = custom; Customization = custom;
PlexSettings = ps; PlexSettings = ps;
Log = log;
PlexContent = content;
PlexEpisodes = plexEp;
} }
public int Version => 22000; public int Version => 22000;
private ISettingsService<CustomizationSettings> Customization { get; set; } private ISettingsService<CustomizationSettings> Customization { get; }
private ISettingsService<PlexSettings> PlexSettings { get; set; } private ISettingsService<PlexSettings> PlexSettings { get; }
private IRepository<RecentlyAddedLog> Log { get; }
private IRepository<PlexContent> PlexContent { get; }
private IRepository<PlexEpisodes> PlexEpisodes { get; }
private static Logger Logger = LogManager.GetCurrentClassLogger(); private static Logger Logger = LogManager.GetCurrentClassLogger();
@ -56,12 +67,46 @@ namespace Ombi.Core.Migration.Migrations
UpdateCustomSettings(); UpdateCustomSettings();
AddNewColumns(con); AddNewColumns(con);
UpdateSchema(con, Version); UpdateSchema(con, Version);
UpdateRecentlyAdded(con);
}
private void UpdateRecentlyAdded(IDbConnection con)
{
var allContent = PlexContent.GetAll();
var content = new HashSet<RecentlyAddedLog>();
foreach (var plexContent in allContent)
{
content.Add(new RecentlyAddedLog
{
AddedAt = DateTime.UtcNow,
ProviderId = plexContent.ProviderId
});
}
Log.BatchInsert(content, "RecentlyAddedLog");
var allEp = PlexEpisodes.GetAll();
content.Clear();
foreach (var ep in allEp)
{
content.Add(new RecentlyAddedLog
{
AddedAt = DateTime.UtcNow,
ProviderId = ep.ProviderId
});
}
Log.BatchInsert(content, "RecentlyAddedLog");
} }
private void AddNewColumns(IDbConnection con) private void AddNewColumns(IDbConnection con)
{ {
con.AlterTable("EmbyContent", "ADD", "AddedAt", true, "VARCHAR(50)"); con.AlterTable("EmbyContent", "ADD", "AddedAt", true, "VARCHAR(50)");
con.AlterTable("EmbyEpisodes", "ADD", "AddedAt", true, "VARCHAR(50)"); con.AlterTable("EmbyEpisodes", "ADD", "AddedAt", true, "VARCHAR(50)");
con.AlterTable("PlexContent", "ADD", "ItemID", true, "VARCHAR(100)");
con.AlterTable("PlexContent", "ADD", "AddedAt", true, "VARCHAR(100)");
} }
private void UpdatePlexSettings() private void UpdatePlexSettings()

View file

@ -62,7 +62,7 @@ namespace Ombi.Core
// Artist is now active // Artist is now active
// Add album // Add album
var albumResult = await Api.AddAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId); var albumResult = await Api.AddAlbum(Settings.ApiKey, Settings.FullUri, request.ReleaseId);
if (!albumResult) if (!albumResult)
{ {
Log.Error("Couldn't add the album to headphones"); Log.Error("Couldn't add the album to headphones");

View file

@ -111,7 +111,7 @@ namespace Ombi.Services.Jobs
} }
// Insert the new items // Insert the new items
var result = Repo.BatchInsert(model, TableName, typeof(EmbyEpisodes).GetPropertyNames()); var result = Repo.BatchInsert(model, TableName);
if (!result) if (!result)
{ {

View file

@ -115,7 +115,8 @@ namespace Ombi.Services.Jobs
ReleaseYear = video.Year, ReleaseYear = video.Year,
Title = video.Title, Title = video.Title,
ProviderId = video.ProviderId, ProviderId = video.ProviderId,
Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, video.RatingKey) Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, video.RatingKey),
ItemId = video.RatingKey
})); }));
} }
} }
@ -145,6 +146,7 @@ namespace Ombi.Services.Jobs
ProviderId = x.ProviderId, ProviderId = x.ProviderId,
Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray(), Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray(),
Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey), Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey),
ItemId= x.RatingKey
})); }));
} }
@ -271,7 +273,8 @@ namespace Ombi.Services.Jobs
ReleaseYear = m.ReleaseYear ?? string.Empty, ReleaseYear = m.ReleaseYear ?? string.Empty,
Title = m.Title, Title = m.Title,
Type = Store.Models.Plex.PlexMediaType.Movie, Type = Store.Models.Plex.PlexMediaType.Movie,
Url = m.Url Url = m.Url,
ItemId = m.ItemId
}); });
} }
} }
@ -311,7 +314,8 @@ namespace Ombi.Services.Jobs
Title = t.Title, Title = t.Title,
Type = Store.Models.Plex.PlexMediaType.Show, Type = Store.Models.Plex.PlexMediaType.Show,
Url = t.Url, Url = t.Url,
Seasons = ByteConverterHelper.ReturnBytes(t.Seasons) Seasons = ByteConverterHelper.ReturnBytes(t.Seasons),
ItemId = t.ItemId
}); });
} }
} }
@ -352,7 +356,7 @@ namespace Ombi.Services.Jobs
ReleaseYear = a.ReleaseYear ?? string.Empty, ReleaseYear = a.ReleaseYear ?? string.Empty,
Title = a.Title, Title = a.Title,
Type = Store.Models.Plex.PlexMediaType.Artist, Type = Store.Models.Plex.PlexMediaType.Artist,
Url = a.Url Url = a.Url,
}); });
} }
} }

View file

@ -134,7 +134,7 @@ namespace Ombi.Services.Jobs
Repo.DeleteAll(TableName); Repo.DeleteAll(TableName);
// Insert the new items // Insert the new items
var result = Repo.BatchInsert(entities.Select(x => x.Key).ToList(), TableName, typeof(PlexEpisodes).GetPropertyNames()); var result = Repo.BatchInsert(entities.Select(x => x.Key).ToList(), TableName);
if (!result) if (!result)
{ {

View file

@ -228,7 +228,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
AddParagraph(sb, info.Overview); AddParagraph(sb, info.Overview);
} }
catch (RequestLimitExceededException limit) catch (Exception limit)
{ {
// We have hit a limit, we need to now wait. // We have hit a limit, we need to now wait.
Thread.Sleep(TimeSpan.FromSeconds(10)); Thread.Sleep(TimeSpan.FromSeconds(10));

View file

@ -0,0 +1,7 @@
namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{
public interface IPlexNewsletter
{
string GetNewsletterHtml(bool test);
}
}

View file

@ -0,0 +1,363 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RecentlyAddedModel.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using NLog;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Jobs.Templates;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using TMDbLib.Objects.Exceptions;
using EmbyMediaType = Ombi.Store.Models.Plex.EmbyMediaType;
using PlexMediaType = Ombi.Store.Models.Plex.PlexMediaType;
namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{
public class PlexRecentlyAddedNewsletter : HtmlTemplateGenerator, IPlexNewsletter
{
public PlexRecentlyAddedNewsletter(IPlexApi api, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmailNotificationSettings> email,
ISettingsService<NewletterSettings> newsletter, IRepository<RecentlyAddedLog> log,
IRepository<PlexContent> embyContent, IRepository<PlexEpisodes> episodes)
{
Api = api;
PlexSettings = plexSettings;
EmailSettings = email;
NewsletterSettings = newsletter;
Content = embyContent;
MovieApi = new TheMovieDbApi();
TvApi = new TvMazeApi();
Episodes = episodes;
RecentlyAddedLog = log;
}
private IPlexApi Api { get; }
private TheMovieDbApi MovieApi { get; }
private TvMazeApi TvApi { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<NewletterSettings> NewsletterSettings { get; }
private IRepository<PlexContent> Content { get; }
private IRepository<PlexEpisodes> Episodes { get; }
private IRepository<RecentlyAddedLog> RecentlyAddedLog { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string GetNewsletterHtml(bool test)
{
try
{
return GetHtml(test);
}
catch (Exception e)
{
Log.Error(e);
return string.Empty;
}
}
private class PlexRecentlyAddedModel
{
public PlexMetadata Metadata { get; set; }
public PlexContent Content { get; set; }
}
private string GetHtml(bool test)
{
var sb = new StringBuilder();
var plexSettings = PlexSettings.GetSettings();
var plexContent = Content.GetAll().ToList();
var series = plexContent.Where(x => x.Type == PlexMediaType.Show).ToList();
var episodes = Episodes.GetAll().ToList();
var movie = plexContent.Where(x => x.Type == PlexMediaType.Movie).ToList();
var recentlyAdded = RecentlyAddedLog.GetAll().ToList();
var firstRun = !recentlyAdded.Any();
var filteredMovies = movie.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList();
var filteredEp = episodes.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList();
var info = new List<PlexRecentlyAddedModel>();
foreach (var m in filteredMovies)
{
var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, m.ItemId);
info.Add(new PlexRecentlyAddedModel
{
Metadata = i,
Content = m
});
}
GenerateMovieHtml(info, sb);
info.Clear();
foreach (var t in series)
{
var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, t.ItemId);
//var ep = filteredEp.Where(x => x.ShowTitle == t.Title);
info.Add(new PlexRecentlyAddedModel
{
Metadata = i,
Content = t
});
//if (ep.Any())
//{
// var episodeList = new List<EmbyEpisodeInformation>();
// foreach (var embyEpisodese in ep)
// {
// var epInfo = Api.GetInformation(embyEpisodese.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Episode,
// embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri);
// episodeList.Add(epInfo.EpisodeInformation);
// }
// info.Add(new EmbyRecentlyAddedModel
// {
// EmbyContent = t,
// EmbyInformation = i,
// EpisodeInformation = episodeList
// });
//}
}
GenerateTvHtml(info, sb);
var template = new RecentlyAddedTemplate();
var html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
if (!test || firstRun)
{
foreach (var a in filteredMovies)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = a.ProviderId,
AddedAt = DateTime.UtcNow
});
}
foreach (var a in filteredEp)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = a.ProviderId,
AddedAt = DateTime.UtcNow
});
}
}
var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray());
Log.Debug(escapedHtml);
return escapedHtml;
}
private void GenerateMovieHtml(IEnumerable<PlexRecentlyAddedModel> recentlyAddedMovies, StringBuilder sb)
{
var movies = recentlyAddedMovies?.ToList() ?? new List<PlexRecentlyAddedModel>();
if (!movies.Any())
{
return;
}
var orderedMovies = movies.OrderByDescending(x => x.Content.AddedAt).ToList();
sb.Append("<h1>New Movies:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var movie in orderedMovies)
{
// We have a try within a try so we can catch the rate limit without ending the loop (finally block)
try
{
try
{
var imdbId = PlexHelper.GetProviderIdFromPlexGuid(movie.Metadata.Video.Guid);
var info = MovieApi.GetMovieInformation(imdbId).Result;
if (info == null)
{
throw new Exception($"Movie with Imdb id {imdbId} returned null from the MovieApi");
}
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/w500{info.BackdropPath}");
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
Header(sb, 3, $"{info.Title} {info.ReleaseDate?.ToString("yyyy") ?? string.Empty}");
EndTag(sb, "a");
if (info.Genres.Any())
{
AddParagraph(sb,
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
AddParagraph(sb, info.Overview);
}
catch (RequestLimitExceededException limit)
{
// We have hit a limit, we need to now wait.
Thread.Sleep(TimeSpan.FromSeconds(10));
Log.Info(limit);
}
}
catch (Exception e)
{
Log.Error(e);
Log.Error("Error for movie with IMDB Id = {0}", movie.Metadata.Video.Guid);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private class TvModel
{
public EmbySeriesInformation Series { get; set; }
public List<EmbyEpisodeInformation> Episodes { get; set; }
}
private void GenerateTvHtml(IEnumerable<PlexRecentlyAddedModel> recenetlyAddedTv, StringBuilder sb)
{
var tv = recenetlyAddedTv?.ToList() ?? new List<PlexRecentlyAddedModel>();
if (!tv.Any())
{
return;
}
var orderedTv = tv.OrderByDescending(x => x.Content.AddedAt).ToList();
// TV
sb.Append("<h1>New Episodes:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv)
{
//var seriesItem = t.EmbyInformation.SeriesInformation;
//var relatedEpisodes = t.EpisodeInformation;
try
{
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(t.Metadata.Directory.Guid)));
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
banner = banner.Replace("http", "https"); // Always use the Https banners
}
AddImageInsideTable(sb, banner);
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
var title = $"{t.Content.Title} {t.Content.ReleaseYear}";
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
Header(sb, 3, title);
EndTag(sb, "a");
//var results = relatedEpisodes.GroupBy(p => p.ParentIndexNumber,
// (key, g) => new
// {
// ParentIndexNumber = key,
// IndexNumber = g.ToList()
// }
//);
// Group the episodes
//foreach (var embyEpisodeInformation in results.OrderBy(x => x.ParentIndexNumber))
//{
// var epSb = new StringBuilder();
// for (var i = 0; i < embyEpisodeInformation.IndexNumber.Count; i++)
// {
// var ep = embyEpisodeInformation.IndexNumber[i];
// if (i < embyEpisodeInformation.IndexNumber.Count)
// {
// epSb.Append($"{ep.IndexNumber},");
// }
// else
// {
// epSb.Append(ep);
// }
// }
// AddParagraph(sb, $"Season: {embyEpisodeInformation.ParentIndexNumber}, Episode: {epSb}");
//}
if (info.genres.Any())
{
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
AddParagraph(sb, string.IsNullOrEmpty(t.Metadata.Directory.Summary) ? t.Metadata.Directory.Summary : info.summary);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private void EndLoopHtml(StringBuilder sb)
{
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
sb.Append("<hr />");
sb.Append("<br />");
sb.Append("<br />");
sb.Append("</td>");
sb.Append("</tr>");
}
}
}

View file

@ -53,18 +53,19 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
public RecentlyAddedNewsletter(IPlexApi api, ISettingsService<PlexSettings> plexSettings, public RecentlyAddedNewsletter(IPlexApi api, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmailNotificationSettings> email, IJobRecord rec, ISettingsService<EmailNotificationSettings> email, IJobRecord rec,
ISettingsService<NewletterSettings> newsletter, ISettingsService<NewletterSettings> newsletter,
IPlexReadOnlyDatabase db, IUserHelper userHelper, IEmbyAddedNewsletter embyNews, IUserHelper userHelper, IEmbyAddedNewsletter embyNews,
ISettingsService<EmbySettings> embyS) ISettingsService<EmbySettings> embyS,
IPlexNewsletter plex)
{ {
JobRecord = rec; JobRecord = rec;
Api = api; Api = api;
PlexSettings = plexSettings; PlexSettings = plexSettings;
EmailSettings = email; EmailSettings = email;
NewsletterSettings = newsletter; NewsletterSettings = newsletter;
PlexDb = db;
UserHelper = userHelper; UserHelper = userHelper;
EmbyNewsletter = embyNews; EmbyNewsletter = embyNews;
EmbySettings = embyS; EmbySettings = embyS;
PlexNewsletter = plex;
} }
private IPlexApi Api { get; } private IPlexApi Api { get; }
@ -75,9 +76,9 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
private ISettingsService<EmailNotificationSettings> EmailSettings { get; } private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<NewletterSettings> NewsletterSettings { get; } private ISettingsService<NewletterSettings> NewsletterSettings { get; }
private IJobRecord JobRecord { get; } private IJobRecord JobRecord { get; }
private IPlexReadOnlyDatabase PlexDb { get; }
private IUserHelper UserHelper { get; } private IUserHelper UserHelper { get; }
private IEmbyAddedNewsletter EmbyNewsletter { get; } private IEmbyAddedNewsletter EmbyNewsletter { get; }
private IPlexNewsletter PlexNewsletter { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private static readonly Logger Log = LogManager.GetCurrentClassLogger();
@ -144,330 +145,17 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
} }
else else
{ {
var sb = new StringBuilder();
var plexSettings = PlexSettings.GetSettings(); var plexSettings = PlexSettings.GetSettings();
Log.Debug("Got Plex Settings"); if (plexSettings.Enable)
{
var html = PlexNewsletter.GetNewsletterHtml(testEmail);
var libs = Api.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri); var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray());
Log.Debug("Getting Plex Library Sections");
var tvSections = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib
Log.Debug("Filtered sections for TV");
var movieSection = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib
Log.Debug("Filtered sections for Movies");
var plexVersion = Api.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri).Version;
var html = string.Empty;
if (plexVersion.StartsWith("1.3"))
{
var tvMetadata = new List<Metadata>();
var movieMetadata = new List<Metadata>();
foreach (var tvSection in tvSections)
{
var item = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri,
tvSection?.Key);
if (item?.MediaContainer?.Metadata != null)
{
tvMetadata.AddRange(item?.MediaContainer?.Metadata);
}
}
Log.Debug("Got RecentlyAdded TV Shows");
foreach (var movie in movieSection)
{
var recentlyAddedMovies = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key);
if (recentlyAddedMovies?.MediaContainer?.Metadata != null)
{
movieMetadata.AddRange(recentlyAddedMovies?.MediaContainer?.Metadata);
}
}
Log.Debug("Got RecentlyAdded Movies");
Log.Debug("Started Generating Movie HTML");
GenerateMovieHtml(movieMetadata, plexSettings, sb);
Log.Debug("Finished Generating Movie HTML");
Log.Debug("Started Generating TV HTML");
GenerateTvHtml(tvMetadata, plexSettings, sb);
Log.Debug("Finished Generating TV HTML");
var template = new RecentlyAddedTemplate();
html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
}
else
{
// Old API
var tvChild = new List<RecentlyAddedChild>();
var movieChild = new List<RecentlyAddedChild>();
foreach (var tvSection in tvSections)
{
var recentlyAddedTv = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, tvSection?.Key);
if (recentlyAddedTv?._children != null)
{
tvChild.AddRange(recentlyAddedTv?._children);
}
}
Log.Debug("Got RecentlyAdded TV Shows");
foreach (var movie in movieSection)
{
var recentlyAddedMovies = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key);
if (recentlyAddedMovies?._children != null)
{
tvChild.AddRange(recentlyAddedMovies?._children);
}
}
Log.Debug("Got RecentlyAdded Movies");
Log.Debug("Started Generating Movie HTML");
GenerateMovieHtml(movieChild, plexSettings, sb);
Log.Debug("Finished Generating Movie HTML");
Log.Debug("Started Generating TV HTML");
GenerateTvHtml(tvChild, plexSettings, sb);
Log.Debug("Finished Generating TV HTML");
var template = new RecentlyAddedTemplate();
html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
}
string escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray());
Log.Debug(escapedHtml); Log.Debug(escapedHtml);
SendNewsletter(newletterSettings, escapedHtml, testEmail); SendNewsletter(newletterSettings, html, testEmail);
} }
} }
private void GenerateMovieHtml(List<RecentlyAddedChild> movies, PlexSettings plexSettings, StringBuilder sb)
{
var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List<RecentlyAddedChild>();
sb.Append("<h1>New Movies:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var movie in orderedMovies)
{
var plexGUID = string.Empty;
try
{
var metaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
movie.ratingKey.ToString());
plexGUID = metaData.Video.Guid;
var imdbId = PlexHelper.GetProviderIdFromPlexGuid(plexGUID);
var info = _movieApi.GetMovieInformation(imdbId).Result;
if (info == null)
{
throw new Exception($"Movie with Imdb id {imdbId} returned null from the MovieApi");
} }
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/w500{info.BackdropPath}");
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
Header(sb, 3, $"{info.Title} {info.ReleaseDate?.ToString("yyyy") ?? string.Empty}");
EndTag(sb, "a");
if (info.Genres.Any())
{
AddParagraph(sb,
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
AddParagraph(sb, info.Overview);
}
catch (Exception e)
{
Log.Error(e);
Log.Error(
"Exception when trying to process a Movie, either in getting the metadata from Plex OR getting the information from TheMovieDB, Plex GUID = {0}",
plexGUID);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private void GenerateMovieHtml(List<Metadata> movies, PlexSettings plexSettings, StringBuilder sb)
{
var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List<Metadata>();
sb.Append("<h1>New Movies:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var movie in orderedMovies)
{
var plexGUID = string.Empty;
try
{
var metaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
movie.ratingKey.ToString());
plexGUID = metaData.Video.Guid;
var imdbId = PlexHelper.GetProviderIdFromPlexGuid(plexGUID);
var info = _movieApi.GetMovieInformation(imdbId).Result;
if (info == null)
{
throw new Exception($"Movie with Imdb id {imdbId} returned null from the MovieApi");
}
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/w500{info.BackdropPath}");
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
Header(sb, 3, $"{info.Title} {info.ReleaseDate?.ToString("yyyy") ?? string.Empty}");
EndTag(sb, "a");
if (info.Genres.Any())
{
AddParagraph(sb,
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
AddParagraph(sb, info.Overview);
}
catch (Exception e)
{
Log.Error(e);
Log.Error(
"Exception when trying to process a Movie, either in getting the metadata from Plex OR getting the information from TheMovieDB, Plex GUID = {0}",
plexGUID);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private void GenerateTvHtml(List<RecentlyAddedChild> tv, PlexSettings plexSettings, StringBuilder sb)
{
var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList();
// TV
sb.Append("<h1>New Episodes:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv)
{
var plexGUID = string.Empty;
try
{
var parentMetaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
t.parentRatingKey.ToString());
plexGUID = parentMetaData.Directory.Guid;
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(plexGUID)));
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
banner = banner.Replace("http", "https"); // Always use the Https banners
}
AddImageInsideTable(sb, banner);
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
var title = $"{t.grandparentTitle} - {t.title} {t.originallyAvailableAt?.Substring(0, 4)}";
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
Header(sb, 3, title);
EndTag(sb, "a");
AddParagraph(sb, $"Season: {t.parentIndex}, Episode: {t.index}");
if (info.genres.Any())
{
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
AddParagraph(sb, string.IsNullOrEmpty(t.summary) ? info.summary : t.summary);
}
catch (Exception e)
{
Log.Error(e);
Log.Error(
"Exception when trying to process a TV Show, either in getting the metadata from Plex OR getting the information from TVMaze, Plex GUID = {0}",
plexGUID);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private void GenerateTvHtml(List<Metadata> tv, PlexSettings plexSettings, StringBuilder sb)
{
var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList();
// TV
sb.Append("<h1>New Episodes:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv)
{
var plexGUID = string.Empty;
try
{
var parentMetaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
t.parentRatingKey.ToString());
plexGUID = parentMetaData.Directory.Guid;
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(plexGUID)));
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
banner = banner.Replace("http", "https"); // Always use the Https banners
}
AddImageInsideTable(sb, banner);
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
var title = $"{t.grandparentTitle} - {t.title} {t.originallyAvailableAt?.Substring(0, 4)}";
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
Header(sb, 3, title);
EndTag(sb, "a");
AddParagraph(sb, $"Season: {t.parentIndex}, Episode: {t.index}");
if (info.genres.Any())
{
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
AddParagraph(sb, string.IsNullOrEmpty(t.summary) ? info.summary : t.summary);
}
catch (Exception e)
{
Log.Error(e);
Log.Error(
"Exception when trying to process a TV Show, either in getting the metadata from Plex OR getting the information from TVMaze, Plex GUID = {0}",
plexGUID);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private void SendMassEmail(string html, string subject, bool testEmail) private void SendMassEmail(string html, string subject, bool testEmail)
{ {
@ -507,7 +195,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
SendMail(settings, message); SendMail(settings, message);
} }
// TODO Emby
private void SendNewsletter(NewletterSettings newletterSettings, string html, bool testEmail = false, string subject = "New Content on Plex!") private void SendNewsletter(NewletterSettings newletterSettings, string html, bool testEmail = false, string subject = "New Content on Plex!")
{ {
Log.Debug("Entering SendNewsletter"); Log.Debug("Entering SendNewsletter");
@ -588,17 +276,5 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
Log.Error(e); Log.Error(e);
} }
} }
private void EndLoopHtml(StringBuilder sb)
{
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
sb.Append("<hr />");
sb.Append("<br />");
sb.Append("<br />");
sb.Append("</td>");
sb.Append("</tr>");
}
} }
} }

View file

@ -7,5 +7,6 @@
public string ReleaseYear { get; set; } public string ReleaseYear { get; set; }
public string ProviderId { get; set; } public string ProviderId { get; set; }
public string Url { get; set; } public string Url { get; set; }
public string ItemId { get; set; }
} }
} }

View file

@ -8,5 +8,6 @@
public string ProviderId { get; set; } public string ProviderId { get; set; }
public int[] Seasons { get; set; } public int[] Seasons { get; set; }
public string Url { get; set; } public string Url { get; set; }
public string ItemId { get; set; }
} }
} }

View file

@ -108,7 +108,9 @@
<Compile Include="Jobs\EmbyEpisodeCacher.cs" /> <Compile Include="Jobs\EmbyEpisodeCacher.cs" />
<Compile Include="Jobs\EmbyUserChecker.cs" /> <Compile Include="Jobs\EmbyUserChecker.cs" />
<Compile Include="Jobs\RadarrCacher.cs" /> <Compile Include="Jobs\RadarrCacher.cs" />
<Compile Include="Jobs\RecentlyAddedNewsletter\PlexRecentlyAddedNewsletter.cs" />
<Compile Include="Jobs\RecentlyAddedNewsletter\EmbyRecentlyAddedNewsletter.cs" /> <Compile Include="Jobs\RecentlyAddedNewsletter\EmbyRecentlyAddedNewsletter.cs" />
<Compile Include="Jobs\RecentlyAddedNewsletter\IPlexNewsletter.cs" />
<Compile Include="Jobs\RecentlyAddedNewsletter\IEmbyAddedNewsletter.cs" /> <Compile Include="Jobs\RecentlyAddedNewsletter\IEmbyAddedNewsletter.cs" />
<Compile Include="Jobs\Templates\MassEmailTemplate.cs" /> <Compile Include="Jobs\Templates\MassEmailTemplate.cs" />
<Compile Include="Jobs\WatcherCacher.cs" /> <Compile Include="Jobs\WatcherCacher.cs" />

View file

@ -25,6 +25,7 @@
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System;
using System.Data.Linq.Mapping; using System.Data.Linq.Mapping;
namespace Ombi.Store.Models.Plex namespace Ombi.Store.Models.Plex
@ -47,5 +48,8 @@ namespace Ombi.Store.Models.Plex
/// Only used for Albums /// Only used for Albums
/// </summary> /// </summary>
public string Artist { get; set; } public string Artist { get; set; }
public string ItemId { get; set; }
public DateTime AddedAt { get; set; }
} }
} }

View file

@ -286,7 +286,7 @@ namespace Ombi.Store.Repository
} }
} }
public bool BatchInsert(IEnumerable<T> entities, string tableName, params string[] values) public bool BatchInsert(IEnumerable<T> entities, string tableName)
{ {
// If we have nothing to update, then it didn't fail... // If we have nothing to update, then it didn't fail...
var enumerable = entities as T[] ?? entities.ToArray(); var enumerable = entities as T[] ?? entities.ToArray();

View file

@ -81,7 +81,7 @@ namespace Ombi.Store.Repository
bool UpdateAll(IEnumerable<T> entity); bool UpdateAll(IEnumerable<T> entity);
Task<bool> UpdateAllAsync(IEnumerable<T> entity); Task<bool> UpdateAllAsync(IEnumerable<T> entity);
bool BatchInsert(IEnumerable<T> entities, string tableName, params string[] values); bool BatchInsert(IEnumerable<T> entities, string tableName);
IEnumerable<T> Custom(Func<IDbConnection, IEnumerable<T>> func); IEnumerable<T> Custom(Func<IDbConnection, IEnumerable<T>> func);
Task<IEnumerable<T>> CustomAsync(Func<IDbConnection, Task<IEnumerable<T>>> func); Task<IEnumerable<T>> CustomAsync(Func<IDbConnection, Task<IEnumerable<T>>> func);

View file

@ -68,6 +68,8 @@ namespace Ombi.Store
[JsonIgnore] [JsonIgnore]
public bool CanApprove => !Approved && !Available; public bool CanApprove => !Approved && !Available;
public string ReleaseId { get; set; }
public bool UserHasRequested(string username) public bool UserHasRequested(string username)
{ {
return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase)); return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase));

View file

@ -174,7 +174,10 @@ CREATE TABLE IF NOT EXISTS PlexContent
Url VARCHAR(100) NOT NULL, Url VARCHAR(100) NOT NULL,
Artist VARCHAR(100), Artist VARCHAR(100),
Seasons BLOB, Seasons BLOB,
Type INTEGER NOT NULL Type INTEGER NOT NULL,
ItemID VARCHAR(100) NOT NULL,
AddedAt VARCHAR(100) NOT NULL
); );
CREATE UNIQUE INDEX IF NOT EXISTS PlexContent_Id ON PlexContent (Id); CREATE UNIQUE INDEX IF NOT EXISTS PlexContent_Id ON PlexContent (Id);

View file

@ -567,16 +567,21 @@ $(document).on("click", ".change-root-folder", function (e) {
e.preventDefault(); e.preventDefault();
var $this = $(this); var $this = $(this);
var $button = $this.parents('.btn-split').children('.change').first(); var $button = $this.parents('.btn-split').children('.change').first();
var rootFolderId = e.target.id var rootFolderId = e.target.id;
var $form = $this.parents('form').first(); var $form = $this.parents('form').first();
var requestId = $button.attr('id');
if ($button.text() === " Loading...") { if ($button.text() === " Loading...") {
return; return;
} }
loadingButton($button.attr('id'), "success"); loadingButton(requestId, "success");
changeRootFolder($form, rootFolderId, function () { changeRootFolder($form, rootFolderId, function () {
if ($('#' + requestId + "rootPathMain").length) {
$('#' + requestId + "currentRootPath").text($this.text);
}
}); });
}); });

View file

@ -178,7 +178,7 @@ namespace Ombi.UI.Modules.Admin
Post["/", true] = async (x, ct) => await SaveAdmin(); Post["/", true] = async (x, ct) => await SaveAdmin();
Post["/requestauth"] = _ => RequestAuthToken(); Post["/requestauth", true] = async (x, ct) => await RequestAuthToken();
Get["/getusers"] = _ => GetUsers(); Get["/getusers"] = _ => GetUsers();
@ -319,7 +319,7 @@ namespace Ombi.UI.Modules.Admin
: new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" }); : new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" });
} }
private Response RequestAuthToken() private async Task<Response> RequestAuthToken()
{ {
var user = this.Bind<PlexAuth>(); var user = this.Bind<PlexAuth>();
@ -335,11 +335,11 @@ namespace Ombi.UI.Modules.Admin
return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" }); return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" });
} }
var oldSettings = PlexService.GetSettings(); var oldSettings = await PlexService.GetSettingsAsync();
if (oldSettings != null) if (oldSettings != null)
{ {
oldSettings.PlexAuthToken = model.user.authentication_token; oldSettings.PlexAuthToken = model.user.authentication_token;
PlexService.SaveSettings(oldSettings); await PlexService.SaveSettingsAsync(oldSettings);
} }
else else
{ {
@ -347,10 +347,14 @@ namespace Ombi.UI.Modules.Admin
{ {
PlexAuthToken = model.user.authentication_token PlexAuthToken = model.user.authentication_token
}; };
PlexService.SaveSettings(newModel); await PlexService.SaveSettingsAsync(newModel);
} }
return Response.AsJson(new { Result = true, AuthToken = model.user.authentication_token }); var server = PlexApi.GetServer(model.user.authentication_token);
var machine =
server.Server.FirstOrDefault(x => x.AccessToken == model.user.authentication_token)?.MachineIdentifier;
return Response.AsJson(new { Result = true, AuthToken = model.user.authentication_token, Identifier = machine });
} }

View file

@ -299,7 +299,7 @@ namespace Ombi.UI.Modules
Status = tv.Status, Status = tv.Status,
ImdbId = tv.ImdbId, ImdbId = tv.ImdbId,
Id = tv.Id, Id = tv.Id,
PosterPath = tv.PosterPath.Contains("http:") ? tv.PosterPath.Replace("http:", "https:") : tv.PosterPath, // We make the poster path https on request, but this is just incase PosterPath = tv.PosterPath?.Contains("http:") ?? false ? tv.PosterPath?.Replace("http:", "https:") : tv.PosterPath ?? string.Empty, // We make the poster path https on request, but this is just incase
ReleaseDate = tv.ReleaseDate, ReleaseDate = tv.ReleaseDate,
ReleaseDateTicks = tv.ReleaseDate.Ticks, ReleaseDateTicks = tv.ReleaseDate.Ticks,
RequestedDate = tv.RequestedDate, RequestedDate = tv.RequestedDate,

View file

@ -1513,6 +1513,7 @@ namespace Ombi.UI.Modules
{ {
Title = albumInfo.title, Title = albumInfo.title,
MusicBrainzId = albumInfo.id, MusicBrainzId = albumInfo.id,
ReleaseId = releaseId,
Overview = albumInfo.disambiguation, Overview = albumInfo.disambiguation,
PosterPath = img, PosterPath = img,
Type = RequestType.Album, Type = RequestType.Album,

View file

@ -67,6 +67,7 @@ namespace Ombi.UI.NinjectModules
Bind<IEmbyEpisodeCacher>().To<EmbyEpisodeCacher>(); Bind<IEmbyEpisodeCacher>().To<EmbyEpisodeCacher>();
Bind<IEmbyUserChecker>().To<EmbyUserChecker>(); Bind<IEmbyUserChecker>().To<EmbyUserChecker>();
Bind<IEmbyAddedNewsletter>().To<EmbyAddedNewsletter>(); Bind<IEmbyAddedNewsletter>().To<EmbyAddedNewsletter>();
Bind<IPlexNewsletter>().To<PlexRecentlyAddedNewsletter>();
Bind<IAnalytics>().To<Analytics>(); Bind<IAnalytics>().To<Analytics>();

View file

@ -222,6 +222,7 @@
if (response.result === true) { if (response.result === true) {
generateNotify("Success!", "success"); generateNotify("Success!", "success");
$('#authToken').val(response.authToken); $('#authToken').val(response.authToken);
$('#MachineIdentifier').val(response.identifier);
} else { } else {
generateNotify(response.message, "warning"); generateNotify(response.message, "warning");
} }

View file

@ -245,7 +245,7 @@
<div>@UI.Requests_RequestedDate: {{requestedDate}}</div> <div>@UI.Requests_RequestedDate: {{requestedDate}}</div>
{{#if admin}} {{#if admin}}
{{#if currentRootPath}} {{#if currentRootPath}}
<div>Root Path: {{currentRootPath}}</div> <div class="{{requestId}}rootPathMain">Root Path: <span id="{{requestId}}currentRootPath">{{currentRootPath}}</span></div>
{{/if}} {{/if}}
{{/if}} {{/if}}
<div> <div>
@ -285,14 +285,14 @@
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" /> <input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
{{#if_eq hasRootFolders true}} {{#if_eq hasRootFolders true}}
<div class="btn-group btn-split"> <div class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-success-outline approve" id="{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button> <button type="button" class="btn btn-sm btn-success-outline" id="changeRootFolderBtn{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span> <span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span> <span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{{#each rootFolders}} {{#each rootFolders}}
<li><a href="#" class="change-root-folder" id="{{id}}">{{path}}</a></li> <li><a href="#" class="change-root-folder" id="{{id}}" requestId="{{requestId}}">{{path}}</a></li>
{{/each}} {{/each}}
</ul> </ul>
</div> </div>

35
Ombi.Updater/AppType.cs Normal file
View file

@ -0,0 +1,35 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: AppType.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace Ombi.Updater
{
public enum AppType
{
Normal,
Console,
Service
}
}

View file

@ -0,0 +1,39 @@
using Ombi.Common;
using Ombi.Common.EnvironmentInfo;
using Ombi.Common.Processes;
namespace Ombi.Updater
{
public class DetectApplicationType
{
public DetectApplicationType()
{
_processProvider = new ProcessProvider();
_serviceProvider = new ServiceProvider(_processProvider);
}
private readonly IServiceProvider _serviceProvider;
private readonly IProcessProvider _processProvider;
public AppType GetAppType()
{
if (OsInfo.IsNotWindows)
{
// Technically it is the console, but it has been renamed for mono (Linux/OS X)
return AppType.Normal;
}
if (_serviceProvider.ServiceExist(ServiceProvider.OmbiServiceName)
)
{
return AppType.Service;
}
if (_processProvider.Exists(ProcessProvider.OmbiProcessName))
{
return AppType.Console;
}
return AppType.Normal;
}
}
}

View file

@ -0,0 +1,57 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: InstallService.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Linq;
using Ombi.Common;
using Ombi.Common.EnvironmentInfo;
using Ombi.Common.Processes;
namespace Ombi.Updater
{
public class InstallService
{
public void Start(string installFolder)
{
var dector = new DetectApplicationType();
var processProvider = new ProcessProvider();
var processId = processProvider.FindProcessByName(ProcessProvider.OmbiProcessName)?.FirstOrDefault()?.Id ?? -1;
// Log if process is -1
var appType = dector.GetAppType();
processProvider.FindProcessByName(ProcessProvider.OmbiProcessName);
if (OsInfo.IsWindows)
{
var terminator = new TerminateOmbi(new ServiceProvider(processProvider), processProvider);
terminator.Terminate(processId);
}
}
}
}

View file

@ -13,7 +13,7 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug\Updater\</OutputPath>
<DefineConstants>DEBUG;</DefineConstants> <DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
@ -22,12 +22,15 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath> <OutputPath>bin\Release\Updater\</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole> <Externalconsole>true</Externalconsole>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="Polly, Version=4.3.0.0, Culture=neutral, PublicKeyToken=c8a3ffc3f8f825cc, processorArchitecture=MSIL"> <Reference Include="Polly, Version=4.3.0.0, Culture=neutral, PublicKeyToken=c8a3ffc3f8f825cc, processorArchitecture=MSIL">
<HintPath>..\packages\Polly-Signed.4.3.0\lib\net45\Polly.dll</HintPath> <HintPath>..\packages\Polly-Signed.4.3.0\lib\net45\Polly.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@ -38,12 +41,20 @@
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="AppType.cs" />
<Compile Include="DetectApplicationType.cs" />
<Compile Include="InstallService.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TerminateOmbi.cs" />
<Compile Include="Updater.cs" /> <Compile Include="Updater.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ombi.Common\Ombi.Common.csproj">
<Project>{bfd45569-90cf-47ca-b575-c7b0ff97f67b}</Project>
<Name>Ombi.Common</Name>
</ProjectReference>
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj"> <ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj">
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project> <Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
<Name>Ombi.Core</Name> <Name>Ombi.Core</Name>

View file

@ -0,0 +1,88 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: TerminateOmbi.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using NLog;
using Ombi.Common;
using Ombi.Common.EnvironmentInfo;
using Ombi.Common.Processes;
using IServiceProvider = Ombi.Common.IServiceProvider;
namespace Ombi.Updater
{
public interface ITerminateOmbi
{
void Terminate(int processId);
}
public class TerminateOmbi : ITerminateOmbi
{
private readonly IServiceProvider _serviceProvider;
private readonly IProcessProvider _processProvider;
private static Logger _logger = LogManager.GetCurrentClassLogger();
public TerminateOmbi(IServiceProvider serviceProvider, IProcessProvider processProvider)
{
_serviceProvider = serviceProvider;
_processProvider = processProvider;
}
public void Terminate(int processId)
{
if (OsInfo.IsWindows)
{
_logger.Info("Stopping all running services");
if (_serviceProvider.ServiceExist(ServiceProvider.OmbiServiceName))
{
try
{
_logger.Info("NzbDrone Service is installed and running");
_serviceProvider.Stop(ServiceProvider.OmbiServiceName);
}
catch (Exception e)
{
_logger.Error(e, "couldn't stop service");
}
}
_logger.Info("Killing all running processes");
_processProvider.KillAll(ProcessProvider.OmbiProcessName);
}
else
{
_logger.Info("Killing all running processes");
_processProvider.KillAll(ProcessProvider.OmbiProcessName);
_processProvider.Kill(processId);
}
}
}
}

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NLog" version="4.3.6" targetFramework="net45" />
<package id="Polly-Signed" version="4.3.0" targetFramework="net45" /> <package id="Polly-Signed" version="4.3.0" targetFramework="net45" />
</packages> </packages>

View file

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 15
VisualStudioVersion = 14.0.25420.1 VisualStudioVersion = 15.0.26206.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.UI", "Ombi.UI\Ombi.UI.csproj", "{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.UI", "Ombi.UI\Ombi.UI.csproj", "{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}"
EndProject EndProject
@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Helpers.Tests", "Ombi.
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Core.Migration", "Ombi.Core.Migration\Ombi.Core.Migration.csproj", "{8406EE57-D533-47C0-9302-C6B5F8C31E55}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Core.Migration", "Ombi.Core.Migration\Ombi.Core.Migration.csproj", "{8406EE57-D533-47C0-9302-C6B5F8C31E55}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Common", "Ombi.Common\Ombi.Common.csproj", "{BFD45569-90CF-47CA-B575-C7B0FF97F67B}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -97,6 +99,10 @@ Global
{8406EE57-D533-47C0-9302-C6B5F8C31E55}.Debug|Any CPU.Build.0 = Debug|Any CPU {8406EE57-D533-47C0-9302-C6B5F8C31E55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8406EE57-D533-47C0-9302-C6B5F8C31E55}.Release|Any CPU.ActiveCfg = Release|Any CPU {8406EE57-D533-47C0-9302-C6B5F8C31E55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8406EE57-D533-47C0-9302-C6B5F8C31E55}.Release|Any CPU.Build.0 = Release|Any CPU {8406EE57-D533-47C0-9302-C6B5F8C31E55}.Release|Any CPU.Build.0 = Release|Any CPU
{BFD45569-90CF-47CA-B575-C7B0FF97F67B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BFD45569-90CF-47CA-B575-C7B0FF97F67B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFD45569-90CF-47CA-B575-C7B0FF97F67B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFD45569-90CF-47CA-B575-C7B0FF97F67B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE