From cdd995652b1cfb0e2ca92f027fee73cd7041f19e Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 1 Feb 2017 21:19:42 +0000 Subject: [PATCH 1/5] Fixed #1038 --- Ombi.UI/Modules/RequestsModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ombi.UI/Modules/RequestsModule.cs b/Ombi.UI/Modules/RequestsModule.cs index 699b19b67..08d6d81ce 100644 --- a/Ombi.UI/Modules/RequestsModule.cs +++ b/Ombi.UI/Modules/RequestsModule.cs @@ -254,7 +254,7 @@ namespace Ombi.UI.Modules Status = tv.Status, ImdbId = tv.ImdbId, 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, ReleaseDateTicks = tv.ReleaseDate.Ticks, RequestedDate = tv.RequestedDate, From 0da49440e22f4659623838153763ce0fc8573563 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 24 Feb 2017 20:59:53 +0000 Subject: [PATCH 2/5] #1163 #117 --- Ombi.Core.Migration/Migrations/Version2200.cs | 53 ++- Ombi.Services/Jobs/EmbyEpisodeCacher.cs | 2 +- Ombi.Services/Jobs/PlexContentCacher.cs | 12 +- Ombi.Services/Jobs/PlexEpisodeCacher.cs | 2 +- .../EmbyRecentlyAddedNewsletter.cs | 2 +- .../IPlexNewsletter.cs | 7 + .../PlexRecentlyAddedNewsletter.cs | 363 ++++++++++++++++++ .../RecentlyAddedNewsletter.cs | 346 +---------------- Ombi.Services/Models/PlexMovie.cs | 1 + Ombi.Services/Models/PlexTvShow.cs | 1 + Ombi.Services/Ombi.Services.csproj | 2 + Ombi.Store/Models/Plex/PlexContent.cs | 4 + .../Repository/BaseGenericRepository.cs | 2 +- Ombi.Store/Repository/IRepository.cs | 2 +- Ombi.Store/SqlTables.sql | 5 +- Ombi.UI/Modules/Admin/AdminModule.cs | 16 +- Ombi.UI/NinjectModules/ServicesModule.cs | 1 + Ombi.UI/Views/Admin/Plex.cshtml | 1 + 18 files changed, 467 insertions(+), 355 deletions(-) create mode 100644 Ombi.Services/Jobs/RecentlyAddedNewsletter/IPlexNewsletter.cs create mode 100644 Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs diff --git a/Ombi.Core.Migration/Migrations/Version2200.cs b/Ombi.Core.Migration/Migrations/Version2200.cs index de9f0d77e..4422846f0 100644 --- a/Ombi.Core.Migration/Migrations/Version2200.cs +++ b/Ombi.Core.Migration/Migrations/Version2200.cs @@ -27,26 +27,37 @@ #endregion +using System; using System.Data; using NLog; using Ombi.Core.SettingModels; using Ombi.Store; +using Ombi.Store.Models; +using Ombi.Store.Models.Plex; +using Ombi.Store.Repository; +using Quartz.Collection; namespace Ombi.Core.Migration.Migrations { [Migration(22000, "v2.20.0.0")] public class Version2200 : BaseMigration, IMigration { - public Version2200(ISettingsService custom, ISettingsService ps) + public Version2200(ISettingsService custom, ISettingsService ps, IRepository log, + IRepository content, IRepository plexEp) { Customization = custom; PlexSettings = ps; + Log = log; + PlexContent = content; + PlexEpisodes = plexEp; } public int Version => 22000; - private ISettingsService Customization { get; set; } - private ISettingsService PlexSettings { get; set; } - + private ISettingsService Customization { get; } + private ISettingsService PlexSettings { get; } + private IRepository Log { get; } + private IRepository PlexContent { get; } + private IRepository PlexEpisodes { get; } private static Logger Logger = LogManager.GetCurrentClassLogger(); @@ -56,12 +67,46 @@ namespace Ombi.Core.Migration.Migrations UpdateCustomSettings(); AddNewColumns(con); UpdateSchema(con, Version); + UpdateRecentlyAdded(con); + } + + private void UpdateRecentlyAdded(IDbConnection con) + { + var allContent = PlexContent.GetAll(); + + var content = new HashSet(); + 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) { con.AlterTable("EmbyContent", "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() diff --git a/Ombi.Services/Jobs/EmbyEpisodeCacher.cs b/Ombi.Services/Jobs/EmbyEpisodeCacher.cs index 0135592cc..387a7dc98 100644 --- a/Ombi.Services/Jobs/EmbyEpisodeCacher.cs +++ b/Ombi.Services/Jobs/EmbyEpisodeCacher.cs @@ -111,7 +111,7 @@ namespace Ombi.Services.Jobs } // Insert the new items - var result = Repo.BatchInsert(model, TableName, typeof(EmbyEpisodes).GetPropertyNames()); + var result = Repo.BatchInsert(model, TableName); if (!result) { diff --git a/Ombi.Services/Jobs/PlexContentCacher.cs b/Ombi.Services/Jobs/PlexContentCacher.cs index 936a7a60b..041374c6b 100644 --- a/Ombi.Services/Jobs/PlexContentCacher.cs +++ b/Ombi.Services/Jobs/PlexContentCacher.cs @@ -115,7 +115,8 @@ namespace Ombi.Services.Jobs ReleaseYear = video.Year, Title = video.Title, 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, Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray(), Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey), + ItemId= x.RatingKey })); } @@ -271,7 +273,8 @@ namespace Ombi.Services.Jobs ReleaseYear = m.ReleaseYear ?? string.Empty, Title = m.Title, 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, Type = Store.Models.Plex.PlexMediaType.Show, 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, Title = a.Title, Type = Store.Models.Plex.PlexMediaType.Artist, - Url = a.Url + Url = a.Url, }); } } diff --git a/Ombi.Services/Jobs/PlexEpisodeCacher.cs b/Ombi.Services/Jobs/PlexEpisodeCacher.cs index e6d1fc9c9..58ebe7fd3 100644 --- a/Ombi.Services/Jobs/PlexEpisodeCacher.cs +++ b/Ombi.Services/Jobs/PlexEpisodeCacher.cs @@ -134,7 +134,7 @@ namespace Ombi.Services.Jobs Repo.DeleteAll(TableName); // 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) { diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs index b78f64dcc..90e32dcd0 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs @@ -228,7 +228,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter AddParagraph(sb, info.Overview); } - catch (RequestLimitExceededException limit) + catch (Exception limit) { // We have hit a limit, we need to now wait. Thread.Sleep(TimeSpan.FromSeconds(10)); diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/IPlexNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/IPlexNewsletter.cs new file mode 100644 index 000000000..f22ccf519 --- /dev/null +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/IPlexNewsletter.cs @@ -0,0 +1,7 @@ +namespace Ombi.Services.Jobs.RecentlyAddedNewsletter +{ + public interface IPlexNewsletter + { + string GetNewsletterHtml(bool test); + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs new file mode 100644 index 000000000..0b37ae813 --- /dev/null +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs @@ -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, + ISettingsService email, + ISettingsService newsletter, IRepository log, + IRepository embyContent, IRepository 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 { get; } + private ISettingsService EmailSettings { get; } + private ISettingsService NewsletterSettings { get; } + private IRepository Content { get; } + private IRepository Episodes { get; } + private IRepository 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(); + 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(); + // 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 recentlyAddedMovies, StringBuilder sb) + { + var movies = recentlyAddedMovies?.ToList() ?? new List(); + if (!movies.Any()) + { + return; + } + var orderedMovies = movies.OrderByDescending(x => x.Content.AddedAt).ToList(); + sb.Append("

New Movies:



"); + sb.Append( + ""); + 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(""); + sb.Append( + "
"); + + 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("


"); + } + + private class TvModel + { + public EmbySeriesInformation Series { get; set; } + public List Episodes { get; set; } + } + private void GenerateTvHtml(IEnumerable recenetlyAddedTv, StringBuilder sb) + { + var tv = recenetlyAddedTv?.ToList() ?? new List(); + + if (!tv.Any()) + { + return; + } + var orderedTv = tv.OrderByDescending(x => x.Content.AddedAt).ToList(); + + // TV + sb.Append("

New Episodes:



"); + sb.Append( + ""); + 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(""); + sb.Append( + "
"); + + 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("


"); + } + + + + + 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("
"); + sb.Append("
"); + sb.Append("
"); + sb.Append(""); + sb.Append(""); + } + + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs index e2bbf5ede..330adf60c 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/RecentlyAddedNewsletter.cs @@ -53,18 +53,19 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter public RecentlyAddedNewsletter(IPlexApi api, ISettingsService plexSettings, ISettingsService email, IJobRecord rec, ISettingsService newsletter, - IPlexReadOnlyDatabase db, IUserHelper userHelper, IEmbyAddedNewsletter embyNews, - ISettingsService embyS) + IUserHelper userHelper, IEmbyAddedNewsletter embyNews, + ISettingsService embyS, + IPlexNewsletter plex) { JobRecord = rec; Api = api; PlexSettings = plexSettings; EmailSettings = email; NewsletterSettings = newsletter; - PlexDb = db; UserHelper = userHelper; EmbyNewsletter = embyNews; EmbySettings = embyS; + PlexNewsletter = plex; } private IPlexApi Api { get; } @@ -75,9 +76,9 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter private ISettingsService EmailSettings { get; } private ISettingsService NewsletterSettings { get; } private IJobRecord JobRecord { get; } - private IPlexReadOnlyDatabase PlexDb { get; } private IUserHelper UserHelper { get; } private IEmbyAddedNewsletter EmbyNewsletter { get; } + private IPlexNewsletter PlexNewsletter { get; } private static readonly Logger Log = LogManager.GetCurrentClassLogger(); @@ -144,331 +145,18 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter } else { - var sb = new StringBuilder(); var plexSettings = PlexSettings.GetSettings(); - Log.Debug("Got Plex Settings"); - - var libs = Api.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri); - 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(); - var movieMetadata = new List(); - 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(); - var movieChild = new List(); - 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); - SendNewsletter(newletterSettings, escapedHtml, testEmail); - } - } - - private void GenerateMovieHtml(List movies, PlexSettings plexSettings, StringBuilder sb) - { - var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List(); - sb.Append("

New Movies:



"); - sb.Append( - ""); - 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(""); - sb.Append( - "
"); - - 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("


"); - } - - private void GenerateMovieHtml(List movies, PlexSettings plexSettings, StringBuilder sb) - { - var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List(); - sb.Append("

New Movies:



"); - sb.Append( - ""); - 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(""); - sb.Append( - "
"); - - 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("


"); - } - - private void GenerateTvHtml(List tv, PlexSettings plexSettings, StringBuilder sb) - { - var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList(); - // TV - sb.Append("

New Episodes:



"); - sb.Append( - ""); - 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(""); - sb.Append( - "
"); - - 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("


"); - } - - private void GenerateTvHtml(List tv, PlexSettings plexSettings, StringBuilder sb) - { - var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList(); - // TV - sb.Append("

New Episodes:



"); - sb.Append( - ""); - foreach (var t in orderedTv) - { - var plexGUID = string.Empty; - try + if (plexSettings.Enable) { + var html = PlexNewsletter.GetNewsletterHtml(testEmail); - 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(""); - sb.Append( - "
"); - - 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); + var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray()); + Log.Debug(escapedHtml); + SendNewsletter(newletterSettings, html, testEmail); } } - sb.Append("


"); } - private void SendMassEmail(string html, string subject, bool testEmail) { var settings = EmailSettings.GetSettings(); @@ -507,7 +195,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter SendMail(settings, message); } - // TODO Emby + private void SendNewsletter(NewletterSettings newletterSettings, string html, bool testEmail = false, string subject = "New Content on Plex!") { Log.Debug("Entering SendNewsletter"); @@ -588,17 +276,5 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter 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("
"); - sb.Append("
"); - sb.Append("
"); - sb.Append(""); - sb.Append(""); - } - } } \ No newline at end of file diff --git a/Ombi.Services/Models/PlexMovie.cs b/Ombi.Services/Models/PlexMovie.cs index f0a55e4ce..540055d38 100644 --- a/Ombi.Services/Models/PlexMovie.cs +++ b/Ombi.Services/Models/PlexMovie.cs @@ -7,5 +7,6 @@ public string ReleaseYear { get; set; } public string ProviderId { get; set; } public string Url { get; set; } + public string ItemId { get; set; } } } diff --git a/Ombi.Services/Models/PlexTvShow.cs b/Ombi.Services/Models/PlexTvShow.cs index 60223c233..445296e0f 100644 --- a/Ombi.Services/Models/PlexTvShow.cs +++ b/Ombi.Services/Models/PlexTvShow.cs @@ -8,5 +8,6 @@ public string ProviderId { get; set; } public int[] Seasons { get; set; } public string Url { get; set; } + public string ItemId { get; set; } } } diff --git a/Ombi.Services/Ombi.Services.csproj b/Ombi.Services/Ombi.Services.csproj index 17093e031..923ea6155 100644 --- a/Ombi.Services/Ombi.Services.csproj +++ b/Ombi.Services/Ombi.Services.csproj @@ -108,7 +108,9 @@ + + diff --git a/Ombi.Store/Models/Plex/PlexContent.cs b/Ombi.Store/Models/Plex/PlexContent.cs index 4a24c4e1f..484bb88e0 100644 --- a/Ombi.Store/Models/Plex/PlexContent.cs +++ b/Ombi.Store/Models/Plex/PlexContent.cs @@ -25,6 +25,7 @@ // ************************************************************************/ #endregion +using System; using System.Data.Linq.Mapping; namespace Ombi.Store.Models.Plex @@ -47,5 +48,8 @@ namespace Ombi.Store.Models.Plex /// Only used for Albums /// public string Artist { get; set; } + + public string ItemId { get; set; } + public DateTime AddedAt { get; set; } } } \ No newline at end of file diff --git a/Ombi.Store/Repository/BaseGenericRepository.cs b/Ombi.Store/Repository/BaseGenericRepository.cs index 48469a8d6..a8593eb2a 100644 --- a/Ombi.Store/Repository/BaseGenericRepository.cs +++ b/Ombi.Store/Repository/BaseGenericRepository.cs @@ -286,7 +286,7 @@ namespace Ombi.Store.Repository } } - public bool BatchInsert(IEnumerable entities, string tableName, params string[] values) + public bool BatchInsert(IEnumerable entities, string tableName) { // If we have nothing to update, then it didn't fail... var enumerable = entities as T[] ?? entities.ToArray(); diff --git a/Ombi.Store/Repository/IRepository.cs b/Ombi.Store/Repository/IRepository.cs index 2901e73c9..618b05133 100644 --- a/Ombi.Store/Repository/IRepository.cs +++ b/Ombi.Store/Repository/IRepository.cs @@ -81,7 +81,7 @@ namespace Ombi.Store.Repository bool UpdateAll(IEnumerable entity); Task UpdateAllAsync(IEnumerable entity); - bool BatchInsert(IEnumerable entities, string tableName, params string[] values); + bool BatchInsert(IEnumerable entities, string tableName); IEnumerable Custom(Func> func); Task> CustomAsync(Func>> func); diff --git a/Ombi.Store/SqlTables.sql b/Ombi.Store/SqlTables.sql index cdf5a2f80..50ce19947 100644 --- a/Ombi.Store/SqlTables.sql +++ b/Ombi.Store/SqlTables.sql @@ -174,7 +174,10 @@ CREATE TABLE IF NOT EXISTS PlexContent Url VARCHAR(100) NOT NULL, Artist VARCHAR(100), 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); diff --git a/Ombi.UI/Modules/Admin/AdminModule.cs b/Ombi.UI/Modules/Admin/AdminModule.cs index a11c473b7..388faf926 100644 --- a/Ombi.UI/Modules/Admin/AdminModule.cs +++ b/Ombi.UI/Modules/Admin/AdminModule.cs @@ -178,7 +178,7 @@ namespace Ombi.UI.Modules.Admin Post["/", true] = async (x, ct) => await SaveAdmin(); - Post["/requestauth"] = _ => RequestAuthToken(); + Post["/requestauth", true] = async (x, ct) => await RequestAuthToken(); 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" }); } - private Response RequestAuthToken() + private async Task RequestAuthToken() { var user = this.Bind(); @@ -335,11 +335,11 @@ namespace Ombi.UI.Modules.Admin return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" }); } - var oldSettings = PlexService.GetSettings(); + var oldSettings = await PlexService.GetSettingsAsync(); if (oldSettings != null) { oldSettings.PlexAuthToken = model.user.authentication_token; - PlexService.SaveSettings(oldSettings); + await PlexService.SaveSettingsAsync(oldSettings); } else { @@ -347,10 +347,14 @@ namespace Ombi.UI.Modules.Admin { 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 }); } diff --git a/Ombi.UI/NinjectModules/ServicesModule.cs b/Ombi.UI/NinjectModules/ServicesModule.cs index 210cdfd3e..7ba02e925 100644 --- a/Ombi.UI/NinjectModules/ServicesModule.cs +++ b/Ombi.UI/NinjectModules/ServicesModule.cs @@ -67,6 +67,7 @@ namespace Ombi.UI.NinjectModules Bind().To(); Bind().To(); Bind().To(); + Bind().To(); Bind().To(); diff --git a/Ombi.UI/Views/Admin/Plex.cshtml b/Ombi.UI/Views/Admin/Plex.cshtml index 50ee63e19..ad8702c33 100644 --- a/Ombi.UI/Views/Admin/Plex.cshtml +++ b/Ombi.UI/Views/Admin/Plex.cshtml @@ -222,6 +222,7 @@ if (response.result === true) { generateNotify("Success!", "success"); $('#authToken').val(response.authToken); + $('#MachineIdentifier').val(response.identifier); } else { generateNotify(response.message, "warning"); } From 3d6264095ee1298b63c40f7c193ba6ff4021053a Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 24 Feb 2017 21:43:36 +0000 Subject: [PATCH 3/5] Bunch of updater files --- Ombi.Common/EnvironmentInfo/OsInfo.cs | 54 +++ Ombi.Common/EnvironmentInfo/PlatformInfo.cs | 42 +++ Ombi.Common/Ombi.Common.csproj | 61 ++++ Ombi.Common/Processes/ProcessInfo.cs | 20 ++ Ombi.Common/Processes/ProcessOutput.cs | 59 ++++ Ombi.Common/Processes/ProcessProvider.cs | 343 ++++++++++++++++++++ Ombi.Common/Properties/AssemblyInfo.cs | 36 ++ Ombi.Common/ServiceProvider.cs | 203 ++++++++++++ Ombi.Common/packages.config | 4 + Ombi.Updater/AppType.cs | 35 ++ Ombi.Updater/DetectApplicationType.cs | 39 +++ Ombi.Updater/InstallService.cs | 57 ++++ Ombi.Updater/Ombi.Updater.csproj | 15 +- Ombi.Updater/TerminateOmbi.cs | 88 +++++ Ombi.Updater/packages.config | 1 + Ombi.sln | 10 +- 16 files changed, 1063 insertions(+), 4 deletions(-) create mode 100644 Ombi.Common/EnvironmentInfo/OsInfo.cs create mode 100644 Ombi.Common/EnvironmentInfo/PlatformInfo.cs create mode 100644 Ombi.Common/Ombi.Common.csproj create mode 100644 Ombi.Common/Processes/ProcessInfo.cs create mode 100644 Ombi.Common/Processes/ProcessOutput.cs create mode 100644 Ombi.Common/Processes/ProcessProvider.cs create mode 100644 Ombi.Common/Properties/AssemblyInfo.cs create mode 100644 Ombi.Common/ServiceProvider.cs create mode 100644 Ombi.Common/packages.config create mode 100644 Ombi.Updater/AppType.cs create mode 100644 Ombi.Updater/DetectApplicationType.cs create mode 100644 Ombi.Updater/InstallService.cs create mode 100644 Ombi.Updater/TerminateOmbi.cs diff --git a/Ombi.Common/EnvironmentInfo/OsInfo.cs b/Ombi.Common/EnvironmentInfo/OsInfo.cs new file mode 100644 index 000000000..3ced6f227 --- /dev/null +++ b/Ombi.Common/EnvironmentInfo/OsInfo.cs @@ -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 + } +} \ No newline at end of file diff --git a/Ombi.Common/EnvironmentInfo/PlatformInfo.cs b/Ombi.Common/EnvironmentInfo/PlatformInfo.cs new file mode 100644 index 000000000..045dc26e3 --- /dev/null +++ b/Ombi.Common/EnvironmentInfo/PlatformInfo.cs @@ -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; } + } +} \ No newline at end of file diff --git a/Ombi.Common/Ombi.Common.csproj b/Ombi.Common/Ombi.Common.csproj new file mode 100644 index 000000000..c8c1a53ef --- /dev/null +++ b/Ombi.Common/Ombi.Common.csproj @@ -0,0 +1,61 @@ + + + + + Debug + AnyCPU + {BFD45569-90CF-47CA-B575-C7B0FF97F67B} + Library + Properties + Ombi.Common + Ombi.Common + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NLog.4.3.6\lib\net45\NLog.dll + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ombi.Common/Processes/ProcessInfo.cs b/Ombi.Common/Processes/ProcessInfo.cs new file mode 100644 index 000000000..1686f4b80 --- /dev/null +++ b/Ombi.Common/Processes/ProcessInfo.cs @@ -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"); + } + } +} diff --git a/Ombi.Common/Processes/ProcessOutput.cs b/Ombi.Common/Processes/ProcessOutput.cs new file mode 100644 index 000000000..dc0edee2d --- /dev/null +++ b/Ombi.Common/Processes/ProcessOutput.cs @@ -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 Lines { get; set; } + + public ProcessOutput() + { + Lines = new List(); + } + + public List Standard + { + get + { + return Lines.Where(c => c.Level == ProcessOutputLevel.Standard).ToList(); + } + } + + public List 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 + } +} \ No newline at end of file diff --git a/Ombi.Common/Processes/ProcessProvider.cs b/Ombi.Common/Processes/ProcessProvider.cs new file mode 100644 index 000000000..86d8d808a --- /dev/null +++ b/Ombi.Common/Processes/ProcessProvider.cs @@ -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 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 onOutputDataReceived = null, Action 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 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 onOutputDataReceived = null, Action 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().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName; + } + + private List 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() + .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); + } + } +} diff --git a/Ombi.Common/Properties/AssemblyInfo.cs b/Ombi.Common/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..af987bb95 --- /dev/null +++ b/Ombi.Common/Properties/AssemblyInfo.cs @@ -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")] diff --git a/Ombi.Common/ServiceProvider.cs b/Ombi.Common/ServiceProvider.cs new file mode 100644 index 000000000..4441e7291 --- /dev/null +++ b/Ombi.Common/ServiceProvider.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/Ombi.Common/packages.config b/Ombi.Common/packages.config new file mode 100644 index 000000000..f05a0e060 --- /dev/null +++ b/Ombi.Common/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Ombi.Updater/AppType.cs b/Ombi.Updater/AppType.cs new file mode 100644 index 000000000..a75a95675 --- /dev/null +++ b/Ombi.Updater/AppType.cs @@ -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 + } +} \ No newline at end of file diff --git a/Ombi.Updater/DetectApplicationType.cs b/Ombi.Updater/DetectApplicationType.cs new file mode 100644 index 000000000..73e50231a --- /dev/null +++ b/Ombi.Updater/DetectApplicationType.cs @@ -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; + } + } +} diff --git a/Ombi.Updater/InstallService.cs b/Ombi.Updater/InstallService.cs new file mode 100644 index 000000000..502f2ae39 --- /dev/null +++ b/Ombi.Updater/InstallService.cs @@ -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); + } + + } + } +} \ No newline at end of file diff --git a/Ombi.Updater/Ombi.Updater.csproj b/Ombi.Updater/Ombi.Updater.csproj index 5e0641ded..881b9badd 100644 --- a/Ombi.Updater/Ombi.Updater.csproj +++ b/Ombi.Updater/Ombi.Updater.csproj @@ -13,7 +13,7 @@ true full false - bin\Debug + bin\Debug\Updater\ DEBUG; prompt 4 @@ -22,12 +22,15 @@ full true - bin\Release + bin\Release\Updater\ prompt 4 true + + ..\packages\NLog.4.3.6\lib\net45\NLog.dll + ..\packages\Polly-Signed.4.3.0\lib\net45\Polly.dll True @@ -38,12 +41,20 @@ + + + + + + {bfd45569-90cf-47ca-b575-c7b0ff97f67b} + Ombi.Common + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} Ombi.Core diff --git a/Ombi.Updater/TerminateOmbi.cs b/Ombi.Updater/TerminateOmbi.cs new file mode 100644 index 000000000..f72bc54d2 --- /dev/null +++ b/Ombi.Updater/TerminateOmbi.cs @@ -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); + } + } + } +} \ No newline at end of file diff --git a/Ombi.Updater/packages.config b/Ombi.Updater/packages.config index 4a06c0f59..6bc1f0299 100644 --- a/Ombi.Updater/packages.config +++ b/Ombi.Updater/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/Ombi.sln b/Ombi.sln index 984651728..2eefc0648 100644 --- a/Ombi.sln +++ b/Ombi.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26206.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.UI", "Ombi.UI\Ombi.UI.csproj", "{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}" EndProject @@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Helpers.Tests", "Ombi. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Core.Migration", "Ombi.Core.Migration\Ombi.Core.Migration.csproj", "{8406EE57-D533-47C0-9302-C6B5F8C31E55}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Common", "Ombi.Common\Ombi.Common.csproj", "{BFD45569-90CF-47CA-B575-C7B0FF97F67B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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}.Release|Any CPU.ActiveCfg = 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 GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 21788818951fc8e2084a378797baa08e82cc7997 Mon Sep 17 00:00:00 2001 From: smcpeck Date: Fri, 24 Feb 2017 16:14:16 -0600 Subject: [PATCH 4/5] Headphones - added releaseID to generic RequestedModel and passing that through to HP request. Their API doesn't request via the MusicBrainzId. --- Ombi.Core/HeadphonesSender.cs | 2 +- Ombi.Store/RequestedModel.cs | 2 ++ Ombi.UI/Modules/SearchModule.cs | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Ombi.Core/HeadphonesSender.cs b/Ombi.Core/HeadphonesSender.cs index f3dab3850..043ea4e2a 100644 --- a/Ombi.Core/HeadphonesSender.cs +++ b/Ombi.Core/HeadphonesSender.cs @@ -62,7 +62,7 @@ namespace Ombi.Core // Artist is now active // 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) { Log.Error("Couldn't add the album to headphones"); diff --git a/Ombi.Store/RequestedModel.cs b/Ombi.Store/RequestedModel.cs index c54d68e5c..c2130d277 100644 --- a/Ombi.Store/RequestedModel.cs +++ b/Ombi.Store/RequestedModel.cs @@ -68,6 +68,8 @@ namespace Ombi.Store [JsonIgnore] public bool CanApprove => !Approved && !Available; + public string ReleaseId { get; set; } + public bool UserHasRequested(string username) { return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase)); diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 76fc5fb62..3be96d360 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -1412,6 +1412,7 @@ namespace Ombi.UI.Modules { Title = albumInfo.title, MusicBrainzId = albumInfo.id, + ReleaseId = releaseId, Overview = albumInfo.disambiguation, PosterPath = img, Type = RequestType.Album, From 5095d30a3c45db5669ae6601d5ab944d140a4690 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sat, 25 Feb 2017 22:52:28 +0000 Subject: [PATCH 5/5] Fixed a slight issue where we could click the change folders button rather than the dropdown arrow #1189 --- Ombi.UI/Content/requests.js | 9 +++++++-- Ombi.UI/Views/Requests/Index.cshtml | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Ombi.UI/Content/requests.js b/Ombi.UI/Content/requests.js index 132bb7232..b902c1560 100644 --- a/Ombi.UI/Content/requests.js +++ b/Ombi.UI/Content/requests.js @@ -567,16 +567,21 @@ $(document).on("click", ".change-root-folder", function (e) { e.preventDefault(); var $this = $(this); 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 requestId = $button.attr('id'); + if ($button.text() === " Loading...") { return; } - loadingButton($button.attr('id'), "success"); + loadingButton(requestId, "success"); changeRootFolder($form, rootFolderId, function () { + if ($('#' + requestId + "rootPathMain").length) { + $('#' + requestId + "currentRootPath").text($this.text); + } }); }); diff --git a/Ombi.UI/Views/Requests/Index.cshtml b/Ombi.UI/Views/Requests/Index.cshtml index e94decd85..710339fe4 100644 --- a/Ombi.UI/Views/Requests/Index.cshtml +++ b/Ombi.UI/Views/Requests/Index.cshtml @@ -245,7 +245,7 @@
@UI.Requests_RequestedDate: {{requestedDate}}
{{#if admin}} {{#if currentRootPath}} -
Root Path: {{currentRootPath}}
+
Root Path: {{currentRootPath}}
{{/if}} {{/if}}
@@ -285,14 +285,14 @@ {{#if_eq hasRootFolders true}}
- +