diff --git a/PlexRequests.Core/CacheKeys.cs b/PlexRequests.Core/CacheKeys.cs index 439953d94..6bb8f4c66 100644 --- a/PlexRequests.Core/CacheKeys.cs +++ b/PlexRequests.Core/CacheKeys.cs @@ -47,5 +47,7 @@ namespace PlexRequests.Core public const string CouchPotatoQueued = "CouchPotatoQueued"; public const string GetBaseUrl = "GetBaseUrl"; + + public const string LastestProductVersion = "LatestProductVersion"; } } \ No newline at end of file diff --git a/PlexRequests.Core/JsonRequestService.cs b/PlexRequests.Core/JsonRequestService.cs index 4808cde3b..deefdfccd 100644 --- a/PlexRequests.Core/JsonRequestService.cs +++ b/PlexRequests.Core/JsonRequestService.cs @@ -49,10 +49,9 @@ namespace PlexRequests.Core var entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId }; var id = Repo.Insert(entity); - // TODO Keep an eye on this, since we are now doing 2 DB update for 1 single request, inserting and then updating model.Id = (int)id; - entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = (int)id, MusicId = model.MusicBrainzId}; + entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = (int)id, MusicId = model.MusicBrainzId }; var result = Repo.Update(entity); return result ? id : -1; diff --git a/PlexRequests.Core/Models/StatusModel.cs b/PlexRequests.Core/Models/StatusModel.cs index 1e04b35e6..62047126e 100644 --- a/PlexRequests.Core/Models/StatusModel.cs +++ b/PlexRequests.Core/Models/StatusModel.cs @@ -24,21 +24,11 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion - -using System.Text.RegularExpressions; - namespace PlexRequests.Core.Models { public class StatusModel { public string Version { get; set; } - public int DBVersion { - get - { - string trimStatus = new Regex("[^0-9]", RegexOptions.Compiled).Replace(Version, string.Empty).PadRight(4, '0'); - return int.Parse(trimStatus); - } - } public bool UpdateAvailable { get; set; } public string UpdateUri { get; set; } public string DownloadUri { get; set; } diff --git a/PlexRequests.Core/Setup.cs b/PlexRequests.Core/Setup.cs index a73a5752d..f00f2a144 100644 --- a/PlexRequests.Core/Setup.cs +++ b/PlexRequests.Core/Setup.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Mono.Data.Sqlite; using NLog; @@ -72,18 +73,19 @@ namespace PlexRequests.Core private int CheckSchema() { - var checker = new StatusChecker(); - var status = checker.GetStatus(); + var productVersion = AssemblyHelper.GetProductVersion(); + var trimStatus = new Regex("[^0-9]", RegexOptions.Compiled).Replace(productVersion, string.Empty).PadRight(4, '0'); + var version = int.Parse(trimStatus); var connection = Db.DbConnection(); var schema = connection.GetSchemaVersion(); if (schema == null) { - connection.CreateSchema(status.DBVersion); // Set the default. + connection.CreateSchema(version); // Set the default. schema = connection.GetSchemaVersion(); } - var version = schema.SchemaVersion; + version = schema.SchemaVersion; return version; } diff --git a/PlexRequests.Services/Interfaces/IJobRecord.cs b/PlexRequests.Services/Interfaces/IJobRecord.cs new file mode 100644 index 000000000..66c3be88c --- /dev/null +++ b/PlexRequests.Services/Interfaces/IJobRecord.cs @@ -0,0 +1,33 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: IJobRecord.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 PlexRequests.Services.Interfaces +{ + public interface IJobRecord + { + void Record(string jobName); + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs index d31bb7aa4..6b905a9b3 100644 --- a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs +++ b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs @@ -41,16 +41,18 @@ namespace PlexRequests.Services.Jobs { public class CouchPotatoCacher : IJob, ICouchPotatoCacher { - public CouchPotatoCacher(ISettingsService cpSettings, ICouchPotatoApi cpApi, ICacheProvider cache) + public CouchPotatoCacher(ISettingsService cpSettings, ICouchPotatoApi cpApi, ICacheProvider cache, IJobRecord rec) { CpSettings = cpSettings; CpApi = cpApi; Cache = cache; + Job = rec; } private ISettingsService CpSettings { get; } private ICacheProvider Cache { get; } private ICouchPotatoApi CpApi { get; } + private IJobRecord Job { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); @@ -74,6 +76,10 @@ namespace PlexRequests.Services.Jobs { Log.Error(ex, "Failed caching queued items from CouchPotato"); } + finally + { + Job.Record(JobNames.CpCacher); + } } } diff --git a/PlexRequests.Services/Jobs/JobNames.cs b/PlexRequests.Services/Jobs/JobNames.cs new file mode 100644 index 000000000..d996ac01e --- /dev/null +++ b/PlexRequests.Services/Jobs/JobNames.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: JobNames.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 PlexRequests.Services +{ + public static class JobNames + { + public const string StoreBackup = "Database Backup"; + public const string CpCacher = "CouchPotato Cacher"; + public const string SonarrCacher = "Sonarr Cacher"; + public const string SrCacher = "SickRage Cacher"; + public const string PlexChecker = "Plex Availability Cacher"; + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/JobRecord.cs b/PlexRequests.Services/Jobs/JobRecord.cs new file mode 100644 index 000000000..fd97f785f --- /dev/null +++ b/PlexRequests.Services/Jobs/JobRecord.cs @@ -0,0 +1,59 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: JobRecord.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.Linq; + +using PlexRequests.Services.Interfaces; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; + +namespace PlexRequests.Services +{ + public class JobRecord : IJobRecord + { + public JobRecord(IRepository repo) + { + Repo = repo; + } + private IRepository Repo { get; } + public void Record(string jobName) + { + var allJobs = Repo.GetAll(); + var storeJob = allJobs.FirstOrDefault(x => x.Name == jobName); + if (storeJob != null) + { + storeJob.LastRun = DateTime.UtcNow; + Repo.Update(storeJob); + } + else + { + var job = new ScheduledJobs { LastRun = DateTime.UtcNow, Name = jobName }; + Repo.Insert(job); + } + } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index 54feb6262..0e29b93d5 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -47,7 +47,7 @@ namespace PlexRequests.Services.Jobs public class PlexAvailabilityChecker : IJob, IAvailabilityChecker { public PlexAvailabilityChecker(ISettingsService plexSettings, ISettingsService auth, IRequestService request, IPlexApi plex, ICacheProvider cache, - INotificationService notify) + INotificationService notify, IJobRecord rec) { Plex = plexSettings; Auth = auth; @@ -55,6 +55,7 @@ namespace PlexRequests.Services.Jobs PlexApi = plex; Cache = cache; Notification = notify; + Job = rec; } private ISettingsService Plex { get; } @@ -64,6 +65,7 @@ namespace PlexRequests.Services.Jobs private IPlexApi PlexApi { get; } private ICacheProvider Cache { get; } private INotificationService Notification { get; } + private IJobRecord Job { get; } public void CheckAndUpdateAll() { @@ -144,6 +146,8 @@ namespace PlexRequests.Services.Jobs RequestService.BatchUpdate(modifiedModel); } + Job.Record(JobNames.PlexChecker); + } public List GetPlexMovies() diff --git a/PlexRequests.Services/Jobs/SickRageCacher.cs b/PlexRequests.Services/Jobs/SickRageCacher.cs index 3591cf7bb..69b50b358 100644 --- a/PlexRequests.Services/Jobs/SickRageCacher.cs +++ b/PlexRequests.Services/Jobs/SickRageCacher.cs @@ -41,11 +41,12 @@ namespace PlexRequests.Services.Jobs { public class SickRageCacher : IJob, ISickRageCacher { - public SickRageCacher(ISettingsService srSettings, ISickRageApi srApi, ICacheProvider cache) + public SickRageCacher(ISettingsService srSettings, ISickRageApi srApi, ICacheProvider cache, IJobRecord rec) { SrSettings = srSettings; SrApi = srApi; Cache = cache; + Job = rec; } private ISettingsService SrSettings { get; } @@ -53,6 +54,7 @@ namespace PlexRequests.Services.Jobs private ISickRageApi SrApi { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); + private IJobRecord Job { get; } public void Queued() { @@ -74,6 +76,10 @@ namespace PlexRequests.Services.Jobs { Log.Error(ex, "Failed caching queued items from SickRage"); } + finally + { + Job.Record(JobNames.SrCacher); + } } } diff --git a/PlexRequests.Services/Jobs/SonarrCacher.cs b/PlexRequests.Services/Jobs/SonarrCacher.cs index 15aef545d..97089da27 100644 --- a/PlexRequests.Services/Jobs/SonarrCacher.cs +++ b/PlexRequests.Services/Jobs/SonarrCacher.cs @@ -35,6 +35,8 @@ using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Services.Interfaces; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; using Quartz; @@ -42,16 +44,18 @@ namespace PlexRequests.Services.Jobs { public class SonarrCacher : IJob, ISonarrCacher { - public SonarrCacher(ISettingsService sonarrSettings, ISonarrApi sonarrApi, ICacheProvider cache) + public SonarrCacher(ISettingsService sonarrSettings, ISonarrApi sonarrApi, ICacheProvider cache, IJobRecord rec) { SonarrSettings = sonarrSettings; SonarrApi = sonarrApi; + Job = rec; Cache = cache; } private ISettingsService SonarrSettings { get; } private ICacheProvider Cache { get; } private ISonarrApi SonarrApi { get; } + private IJobRecord Job { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); @@ -75,6 +79,10 @@ namespace PlexRequests.Services.Jobs { Log.Error(ex, "Failed caching queued items from Sonarr"); } + finally + { + Job.Record(JobNames.SonarrCacher); + } } } diff --git a/PlexRequests.Services/Jobs/StoreBackup.cs b/PlexRequests.Services/Jobs/StoreBackup.cs index 2d63987b0..64a1fbc57 100644 --- a/PlexRequests.Services/Jobs/StoreBackup.cs +++ b/PlexRequests.Services/Jobs/StoreBackup.cs @@ -30,7 +30,10 @@ using System.Linq; using NLog; +using PlexRequests.Services.Interfaces; using PlexRequests.Store; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; using Quartz; @@ -40,12 +43,14 @@ namespace PlexRequests.Services.Jobs { public class StoreBackup : IJob { - public StoreBackup(ISqliteConfiguration sql) + public StoreBackup(ISqliteConfiguration sql, IJobRecord rec) { Sql = sql; + JobRecord = rec; } private ISqliteConfiguration Sql { get; } + private IJobRecord JobRecord { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); @@ -82,6 +87,10 @@ namespace PlexRequests.Services.Jobs Log.Warn(e); Log.Warn("Exception when trying to copy the backup."); } + finally + { + JobRecord.Record(JobNames.StoreBackup); + } } } diff --git a/PlexRequests.Services/PlexRequests.Services.csproj b/PlexRequests.Services/PlexRequests.Services.csproj index 1b088294a..72b0e9372 100644 --- a/PlexRequests.Services/PlexRequests.Services.csproj +++ b/PlexRequests.Services/PlexRequests.Services.csproj @@ -93,6 +93,9 @@ + + + diff --git a/PlexRequests.Store/Models/ScheduledJobs.cs b/PlexRequests.Store/Models/ScheduledJobs.cs new file mode 100644 index 000000000..7c242f0bd --- /dev/null +++ b/PlexRequests.Store/Models/ScheduledJobs.cs @@ -0,0 +1,39 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: LogEntity.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 Dapper.Contrib.Extensions; + +namespace PlexRequests.Store.Models +{ + [Table("ScheduledJobs")] + public class ScheduledJobs : Entity + { + public string Name { get; set; } + public DateTime LastRun { get; set; } + } +} diff --git a/PlexRequests.Store/PlexRequests.Store.csproj b/PlexRequests.Store/PlexRequests.Store.csproj index ae047a192..927969b86 100644 --- a/PlexRequests.Store/PlexRequests.Store.csproj +++ b/PlexRequests.Store/PlexRequests.Store.csproj @@ -63,6 +63,7 @@ + diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index 7392c4efc..7a1cbed63 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -45,5 +45,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS Logs_Id ON Logs (Id); CREATE TABLE IF NOT EXISTS DBInfo ( SchemaVersion INTEGER +); -); \ No newline at end of file +CREATE TABLE IF NOT EXISTS ScheduledJobs +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Name varchar(100) NOT NULL, + LastRun varchar(100) NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS ScheduledJobs_Id ON ScheduledJobs (Id); \ No newline at end of file diff --git a/PlexRequests.UI/Bootstrapper.cs b/PlexRequests.UI/Bootstrapper.cs index fc9908b06..ed1cd1bfb 100644 --- a/PlexRequests.UI/Bootstrapper.cs +++ b/PlexRequests.UI/Bootstrapper.cs @@ -87,8 +87,10 @@ namespace PlexRequests.UI // Repo's container.Register, GenericRepository>(); + container.Register, GenericRepository>(); container.Register(); container.Register(); + container.Register(); // Services container.Register(); diff --git a/PlexRequests.UI/Content/custom.css b/PlexRequests.UI/Content/custom.css index 520b2cc39..c9ec38106 100644 --- a/PlexRequests.UI/Content/custom.css +++ b/PlexRequests.UI/Content/custom.css @@ -220,3 +220,7 @@ label { border-radius: 0 0.25rem 0.25rem 0 !important; padding: 12px 8px; } +#updateAvailable { + background-color: #ffa400; + text-align: center; } + diff --git a/PlexRequests.UI/Content/custom.min.css b/PlexRequests.UI/Content/custom.min.css index 34e07289c..e5e3c6f8d 100644 --- a/PlexRequests.UI/Content/custom.min.css +++ b/PlexRequests.UI/Content/custom.min.css @@ -1 +1 @@ -@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.btn{border-radius:.25rem !important;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;border-radius:0;box-shadow:0 0 0 !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.navbar .nav a .fa,.dropdown-menu a .fa{font-size:130%;top:1px;position:relative;display:inline-block;margin-right:5px;}.dropdown-menu a .fa{top:2px;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}.scroll-top-wrapper{position:fixed;opacity:0;visibility:hidden;overflow:hidden;text-align:center;z-index:99999999;background-color:#4e5d6c;color:#eee;width:50px;height:48px;line-height:48px;right:30px;bottom:30px;padding-top:2px;border-top-left-radius:10px;border-top-right-radius:10px;border-bottom-right-radius:10px;border-bottom-left-radius:10px;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.scroll-top-wrapper i.fa{line-height:inherit;}.no-search-results{text-align:center;}.no-search-results .no-search-results-icon{font-size:10em;color:#4e5d6c;}.no-search-results .no-search-results-text{margin:20px 0;color:#ccc;}.form-control-search{padding:13px 105px 13px 16px;height:100%;}.form-control-withbuttons{padding-right:105px;}.input-group-addon .btn-group{position:absolute;right:45px;z-index:3;top:13px;box-shadow:0 0 0;}.input-group-addon .btn-group .btn{border:1px solid rgba(255,255,255,.7) !important;padding:3px 12px;color:rgba(255,255,255,.7) !important;}.btn-split .btn{border-radius:0 !important;}.btn-split .btn:not(.dropdown-toggle){border-radius:.25rem 0 0 .25rem !important;}.btn-split .btn.dropdown-toggle{border-radius:0 .25rem .25rem 0 !important;padding:12px 8px;} \ No newline at end of file +@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.btn{border-radius:.25rem !important;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;border-radius:0;box-shadow:0 0 0 !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.navbar .nav a .fa,.dropdown-menu a .fa{font-size:130%;top:1px;position:relative;display:inline-block;margin-right:5px;}.dropdown-menu a .fa{top:2px;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}.scroll-top-wrapper{position:fixed;opacity:0;visibility:hidden;overflow:hidden;text-align:center;z-index:99999999;background-color:#4e5d6c;color:#eee;width:50px;height:48px;line-height:48px;right:30px;bottom:30px;padding-top:2px;border-top-left-radius:10px;border-top-right-radius:10px;border-bottom-right-radius:10px;border-bottom-left-radius:10px;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.scroll-top-wrapper i.fa{line-height:inherit;}.no-search-results{text-align:center;}.no-search-results .no-search-results-icon{font-size:10em;color:#4e5d6c;}.no-search-results .no-search-results-text{margin:20px 0;color:#ccc;}.form-control-search{padding:13px 105px 13px 16px;height:100%;}.form-control-withbuttons{padding-right:105px;}.input-group-addon .btn-group{position:absolute;right:45px;z-index:3;top:13px;box-shadow:0 0 0;}.input-group-addon .btn-group .btn{border:1px solid rgba(255,255,255,.7) !important;padding:3px 12px;color:rgba(255,255,255,.7) !important;}.btn-split .btn{border-radius:0 !important;}.btn-split .btn:not(.dropdown-toggle){border-radius:.25rem 0 0 .25rem !important;}.btn-split .btn.dropdown-toggle{border-radius:0 .25rem .25rem 0 !important;padding:12px 8px;}#updateAvailable{background-color:#ffa400;text-align:center;} \ No newline at end of file diff --git a/PlexRequests.UI/Content/custom.scss b/PlexRequests.UI/Content/custom.scss index e2324512b..c9d0f60a8 100644 --- a/PlexRequests.UI/Content/custom.scss +++ b/PlexRequests.UI/Content/custom.scss @@ -7,8 +7,7 @@ $warning-colour: #f0ad4e; $danger-colour: #d9534f; $success-colour: #5cb85c; $i: -!important -; +!important; @media (min-width: 768px ) { .row { @@ -279,4 +278,9 @@ $border-radius: 10px; .btn-split .btn.dropdown-toggle { border-radius: 0 .25rem .25rem 0 $i; padding: 12px 8px; +} + +#updateAvailable { + background-color: rgb(255, 164, 0); + text-align: center; } \ No newline at end of file diff --git a/PlexRequests.UI/Jobs/CustomJobFactory.cs b/PlexRequests.UI/Jobs/CustomJobFactory.cs index 0337999cb..643222844 100644 --- a/PlexRequests.UI/Jobs/CustomJobFactory.cs +++ b/PlexRequests.UI/Jobs/CustomJobFactory.cs @@ -36,7 +36,7 @@ using Quartz.Spi; namespace PlexRequests.UI.Jobs { /// - /// The custom job factory we are using so we are able to use our IoC container with DI in our Jobs. + /// The custom job factory we are using so we are able to use our IoC container with DI in our JobNames. /// /// public class CustomJobFactory : IJobFactory diff --git a/PlexRequests.UI/Models/JsonUpdateAvailableModel.cs b/PlexRequests.UI/Models/JsonUpdateAvailableModel.cs new file mode 100644 index 000000000..38931f86a --- /dev/null +++ b/PlexRequests.UI/Models/JsonUpdateAvailableModel.cs @@ -0,0 +1,33 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: JsonUpdateAvailableModel.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 PlexRequests.UI.Models +{ + public class JsonUpdateAvailableModel + { + public bool UpdateAvailable { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index 71885bdc5..6c93b00d1 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -295,8 +295,8 @@ namespace PlexRequests.UI.Modules } var result = CpService.SaveSettings(couchPotatoSettings); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for CouchPotato!" } + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for CouchPotato!" } : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } @@ -429,7 +429,7 @@ namespace PlexRequests.UI.Modules finally { NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); - } + } return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" }); } @@ -464,7 +464,7 @@ namespace PlexRequests.UI.Modules { var checker = new StatusChecker(); var status = checker.GetStatus(); - var md = new Markdown(); + var md = new Markdown(new MarkdownOptions { AutoNewLines = true }); status.ReleaseNotes = md.Transform(status.ReleaseNotes); return View["Status", status]; } @@ -623,7 +623,7 @@ namespace PlexRequests.UI.Modules { JsonSettings.MaxJsonLength = int.MaxValue; var allLogs = LogsRepo.GetAll().OrderByDescending(x => x.Id).Take(200); - var model = new DatatablesModel {Data = new List()}; + var model = new DatatablesModel { Data = new List() }; foreach (var l in allLogs) { l.DateString = l.Date.ToString("G"); @@ -650,7 +650,7 @@ namespace PlexRequests.UI.Modules settings.Level = level; LogService.SaveSettings(settings); - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"The new log level is now {newLevel}"}); + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"The new log level is now {newLevel}" }); } private Negotiator Headphones() @@ -673,7 +673,7 @@ namespace PlexRequests.UI.Modules Log.Trace(settings.DumpJson()); var result = HeadphonesService.SaveSettings(settings); - + Log.Info("Saved headphones settings, result: {0}", result); return Response.AsJson(result ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Headphones!" } diff --git a/PlexRequests.UI/Modules/BaseAuthModule.cs b/PlexRequests.UI/Modules/BaseAuthModule.cs index 3dcf930da..1cb5fe225 100644 --- a/PlexRequests.UI/Modules/BaseAuthModule.cs +++ b/PlexRequests.UI/Modules/BaseAuthModule.cs @@ -30,8 +30,11 @@ using Nancy.Extensions; using PlexRequests.UI.Models; using System; +using Nancy.Security; + using PlexRequests.Core; using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; namespace PlexRequests.UI.Modules { @@ -52,6 +55,8 @@ namespace PlexRequests.UI.Modules } } + protected bool IsAdmin => Context.CurrentUser.IsAuthenticated(); + protected int DateTimeOffset { get @@ -87,5 +92,7 @@ namespace PlexRequests.UI.Modules : null; } + + } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 094f07171..92bcb1f38 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -124,14 +124,6 @@ namespace PlexRequests.UI.Modules private IHeadphonesApi HeadphonesApi { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); - private bool IsAdmin - { - get - { - return Context.CurrentUser.IsAuthenticated(); - } - } - private Negotiator RequestLoad() { var settings = PrService.GetSettings(); @@ -626,7 +618,7 @@ namespace PlexRequests.UI.Modules Status = showInfo.status, RequestedDate = DateTime.UtcNow, Approved = false, - RequestedUsers = new List() { Username }, + RequestedUsers = new List { Username }, Issues = IssueState.None, ImdbId = showInfo.externals?.imdb ?? string.Empty, SeasonCount = showInfo.seasonCount @@ -802,7 +794,7 @@ namespace PlexRequests.UI.Modules } var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); - sender.AddAlbum(model); + sender.AddAlbum(model).Wait(); model.Approved = true; RequestService.AddRequest(model); diff --git a/PlexRequests.UI/Modules/UpdateCheckerModule.cs b/PlexRequests.UI/Modules/UpdateCheckerModule.cs new file mode 100644 index 000000000..2223b9a4f --- /dev/null +++ b/PlexRequests.UI/Modules/UpdateCheckerModule.cs @@ -0,0 +1,79 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UpdateCheckerModule.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 Nancy; + +using NLog; + +using PlexRequests.Core; +using PlexRequests.Helpers; +using PlexRequests.UI.Models; + +namespace PlexRequests.UI.Modules +{ + public class UpdateCheckerModule : BaseAuthModule + { + public UpdateCheckerModule(ICacheProvider provider) : base("updatechecker") + { + Cache = provider; + + Get["/"] = _ => CheckLatestVersion(); + } + + private ICacheProvider Cache { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + private Response CheckLatestVersion() + { + try + { + if (!IsAdmin) + { + return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = false }); + } + + var checker = new StatusChecker(); + var release = Cache.GetOrSet(CacheKeys.LastestProductVersion, () => checker.GetStatus(), 30); + + if (release.UpdateAvailable) + { + return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = true}); + } + + return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = false }); + } + catch (Exception e) + { + Log.Warn("Exception Thrown when attempting to check the status"); + Log.Warn(e); + return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = false }); + } + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 45668bc1a..f836325ac 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -185,12 +185,14 @@ + + diff --git a/PlexRequests.UI/Views/Shared/_Layout.cshtml b/PlexRequests.UI/Views/Shared/_Layout.cshtml index a61aae9c3..ae61d4690 100644 --- a/PlexRequests.UI/Views/Shared/_Layout.cshtml +++ b/PlexRequests.UI/Views/Shared/_Layout.cshtml @@ -34,7 +34,7 @@ Plex Requests - + +
@@ -82,6 +83,24 @@ $(function () { + var urlBase = '@Html.GetBaseUrl()'; + var url = createBaseUrl(urlBase, '/updatechecker'); + $.ajax({ + type: "GET", + url: url, + dataType: "json", + success: function (response) { + if (response.updateAvailable) { + var status = createBaseUrl(urlBase, '/admin/status'); + $('#updateAvailable').html("There is a new update available! Click Here!"); + $('#updateAvailable').removeAttr("hidden"); + } + }, + error: function (e) { + console.log(e); + } + }); + $(document).on('scroll', function () { if ($(window).scrollTop() > 100) {