diff --git a/Ombi.Api.Interfaces/IEmbyApi.cs b/Ombi.Api.Interfaces/IEmbyApi.cs new file mode 100644 index 000000000..7a2e4f6c4 --- /dev/null +++ b/Ombi.Api.Interfaces/IEmbyApi.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Ombi.Api.Models.Emby; + +namespace Ombi.Api.Interfaces +{ + public interface IEmbyApi + { + EmbyItemContainer GetAllMovies(string apiKey, string userId, Uri baseUri); + EmbyItemContainer GetAllShows(string apiKey, string userId, Uri baseUri); + EmbyItemContainer GetAllEpisodes(string apiKey, string userId, Uri baseUri); + List GetUsers(Uri baseUri, string apiKey); + EmbyItemContainer ViewLibrary(string apiKey, string userId, Uri baseUri); + EmbyInformation GetInformation(string mediaId, EmbyMediaType type, string apiKey, string userId, Uri baseUri); + EmbyUser LogIn(string username, string password, string apiKey, Uri baseUri); + } +} \ No newline at end of file diff --git a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj index c8c1ca938..172c009e4 100644 --- a/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj +++ b/Ombi.Api.Interfaces/Ombi.Api.Interfaces.csproj @@ -56,6 +56,7 @@ + diff --git a/Ombi.Api.Models/Emby/EmbyChapter.cs b/Ombi.Api.Models/Emby/EmbyChapter.cs new file mode 100644 index 000000000..9677eae76 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyChapter.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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.Api.Models.Emby +{ + public class EmbyChapter + { + public long StartPositionTicks { get; set; } + public string Name { get; set; } + } + +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyConfiguration.cs b/Ombi.Api.Models/Emby/EmbyConfiguration.cs new file mode 100644 index 000000000..4df656cce --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyConfiguration.cs @@ -0,0 +1,47 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyUser.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.Api.Models.Emby +{ + public class EmbyConfiguration + { + public bool PlayDefaultAudioTrack { get; set; } + public bool DisplayMissingEpisodes { get; set; } + public bool DisplayUnairedEpisodes { get; set; } + public object[] GroupedFolders { get; set; } + public string SubtitleMode { get; set; } + public bool DisplayCollectionsView { get; set; } + public bool EnableLocalPassword { get; set; } + public object[] OrderedViews { get; set; } + public object[] LatestItemsExcludes { get; set; } + public bool HidePlayedInLatest { get; set; } + public bool RememberAudioSelections { get; set; } + public bool RememberSubtitleSelections { get; set; } + public bool EnableNextEpisodeAutoPlay { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyEpisodeInformation.cs b/Ombi.Api.Models/Emby/EmbyEpisodeInformation.cs new file mode 100644 index 000000000..be173faf9 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyEpisodeInformation.cs @@ -0,0 +1,97 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyEpisodeInformation.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; + +namespace Ombi.Api.Models.Emby +{ + public class EmbyEpisodeInformation + { + public string Name { get; set; } + public string ServerId { get; set; } + public string Id { get; set; } + public string Etag { get; set; } + public DateTime DateCreated { get; set; } + public bool CanDelete { get; set; } + public bool CanDownload { get; set; } + public bool SupportsSync { get; set; } + public string Container { get; set; } + public string SortName { get; set; } + public DateTime PremiereDate { get; set; } + public EmbyExternalurl[] ExternalUrls { get; set; } + public EmbyMediasource[] MediaSources { get; set; } + public string Path { get; set; } + public string Overview { get; set; } + public object[] Taglines { get; set; } + public object[] Genres { get; set; } + public string[] SeriesGenres { get; set; } + public int CommunityRating { get; set; } + public int VoteCount { get; set; } + public long RunTimeTicks { get; set; } + public string PlayAccess { get; set; } + public int ProductionYear { get; set; } + public bool IsPlaceHolder { get; set; } + public int IndexNumber { get; set; } + public int ParentIndexNumber { get; set; } + public object[] RemoteTrailers { get; set; } + public EmbyProviderids ProviderIds { get; set; } + public bool IsHD { get; set; } + public bool IsFolder { get; set; } + public string ParentId { get; set; } + public string Type { get; set; } + public object[] People { get; set; } + public object[] Studios { get; set; } + public string ParentLogoItemId { get; set; } + public string ParentBackdropItemId { get; set; } + public string[] ParentBackdropImageTags { get; set; } + public int LocalTrailerCount { get; set; } + public EmbyUserdata UserData { get; set; } + public string SeriesName { get; set; } + public string SeriesId { get; set; } + public string SeasonId { get; set; } + public string DisplayPreferencesId { get; set; } + public object[] Tags { get; set; } + public object[] Keywords { get; set; } + public string SeriesPrimaryImageTag { get; set; } + public string SeasonName { get; set; } + public EmbyMediastream[] MediaStreams { get; set; } + public string VideoType { get; set; } + public EmbyImagetags ImageTags { get; set; } + public object[] BackdropImageTags { get; set; } + public object[] ScreenshotImageTags { get; set; } + public string ParentLogoImageTag { get; set; } + public string SeriesStudio { get; set; } + public EmbySeriesstudioinfo SeriesStudioInfo { get; set; } + public string ParentThumbItemId { get; set; } + public string ParentThumbImageTag { get; set; } + public EmbyChapter[] Chapters { get; set; } + public string LocationType { get; set; } + public string MediaType { get; set; } + public object[] LockedFields { get; set; } + public bool LockData { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyEpisodeItem.cs b/Ombi.Api.Models/Emby/EmbyEpisodeItem.cs new file mode 100644 index 000000000..a86552727 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyEpisodeItem.cs @@ -0,0 +1,69 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyEpisodeItem.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; + +namespace Ombi.Api.Models.Emby +{ + public class EmbyEpisodeItem + { + public string Name { get; set; } + public string ServerId { get; set; } + public string Id { get; set; } + public string Container { get; set; } + public DateTime PremiereDate { get; set; } + public float CommunityRating { get; set; } + public long RunTimeTicks { get; set; } + public string PlayAccess { get; set; } + public int ProductionYear { get; set; } + public bool IsPlaceHolder { get; set; } + public int IndexNumber { get; set; } + public int ParentIndexNumber { get; set; } + public bool IsHD { get; set; } + public bool IsFolder { get; set; } + public string Type { get; set; } + public string ParentLogoItemId { get; set; } + public string ParentBackdropItemId { get; set; } + public string[] ParentBackdropImageTags { get; set; } + public int LocalTrailerCount { get; set; } + public EmbyUserdata UserData { get; set; } + public string SeriesName { get; set; } + public string SeriesId { get; set; } + public string SeasonId { get; set; } + public string SeriesPrimaryImageTag { get; set; } + public string SeasonName { get; set; } + public string VideoType { get; set; } + public EmbyImagetags ImageTags { get; set; } + public object[] BackdropImageTags { get; set; } + public string ParentLogoImageTag { get; set; } + public string ParentThumbItemId { get; set; } + public string ParentThumbImageTag { get; set; } + public string LocationType { get; set; } + public string MediaType { get; set; } + public bool HasSubtitles { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyExternalurl.cs b/Ombi.Api.Models/Emby/EmbyExternalurl.cs new file mode 100644 index 000000000..2d7de2a3c --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyExternalurl.cs @@ -0,0 +1,42 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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.Api.Models.Emby +{ + public class EmbyExternalurl + { + public string Name { get; set; } + public string Url { get; set; } + } + + + + + + +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyImagetags.cs b/Ombi.Api.Models/Emby/EmbyImagetags.cs new file mode 100644 index 000000000..cf36ae696 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyImagetags.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyLibrary.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.Api.Models.Emby +{ + public class EmbyImagetags + { + public string Primary { get; set; } + public string Logo { get; set; } + public string Thumb { get; set; } + + public string Banner { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyInformation.cs b/Ombi.Api.Models/Emby/EmbyInformation.cs new file mode 100644 index 000000000..8edb432ba --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyInformation.cs @@ -0,0 +1,35 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyInformation.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.Api.Models.Emby +{ + public class EmbyInformation + { + public EmbySeriesInformation SeriesInformation { get; set; } + public EmbyMovieInformation MovieInformation { get; set; } + public EmbyEpisodeInformation EpisodeInformation { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyItem.cs b/Ombi.Api.Models/Emby/EmbyItem.cs new file mode 100644 index 000000000..85d41ddf2 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyItem.cs @@ -0,0 +1,47 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyLibrary.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.Api.Models.Emby +{ + public class EmbyLibrary + { + public string Name { get; set; } + public string ServerId { get; set; } + public string Id { get; set; } + public bool HasDynamicCategories { get; set; } + public string PlayAccess { get; set; } + public bool IsFolder { get; set; } + public string Type { get; set; } + public EmbyUserdata UserData { get; set; } + public int ChildCount { get; set; } + public string CollectionType { get; set; } + public string OriginalCollectionType { get; set; } + public EmbyImagetags ImageTags { get; set; } + public object[] BackdropImageTags { get; set; } + public string LocationType { get; set; } + } + +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyItemContainer.cs b/Ombi.Api.Models/Emby/EmbyItemContainer.cs new file mode 100644 index 000000000..10b8c0a71 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyItemContainer.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyItemContainer.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.Collections.Generic; + +namespace Ombi.Api.Models.Emby +{ + public class EmbyItemContainer + { + public List Items { get; set; } + public int TotalRecordCount { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyMediaType.cs b/Ombi.Api.Models/Emby/EmbyMediaType.cs new file mode 100644 index 000000000..0ec18ad4e --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyMediaType.cs @@ -0,0 +1,36 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyMediaType.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.Api.Models.Emby +{ + public enum EmbyMediaType + { + Movie = 0, + Series = 1, + Music = 2, + Episode = 3 + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyMediasource.cs b/Ombi.Api.Models/Emby/EmbyMediasource.cs new file mode 100644 index 000000000..bc3f4122c --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyMediasource.cs @@ -0,0 +1,59 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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.Api.Models.Emby +{ + public class EmbyMediasource + { + public string Protocol { get; set; } + public string Id { get; set; } + public string Path { get; set; } + public string Type { get; set; } + public string Container { get; set; } + public string Name { get; set; } + public bool IsRemote { get; set; } + public string ETag { get; set; } + public long RunTimeTicks { get; set; } + public bool ReadAtNativeFramerate { get; set; } + public bool SupportsTranscoding { get; set; } + public bool SupportsDirectStream { get; set; } + public bool SupportsDirectPlay { get; set; } + public bool IsInfiniteStream { get; set; } + public bool RequiresOpening { get; set; } + public bool RequiresClosing { get; set; } + public bool SupportsProbing { get; set; } + public string VideoType { get; set; } + public EmbyMediastream[] MediaStreams { get; set; } + public object[] PlayableStreamFileNames { get; set; } + public object[] Formats { get; set; } + public int Bitrate { get; set; } + public EmbyRequiredhttpheaders RequiredHttpHeaders { get; set; } + public int DefaultAudioStreamIndex { get; set; } + + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyMediastream.cs b/Ombi.Api.Models/Emby/EmbyMediastream.cs new file mode 100644 index 000000000..75aff476b --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyMediastream.cs @@ -0,0 +1,64 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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.Api.Models.Emby +{ + public class EmbyMediastream + { + public string Codec { get; set; } + public string Language { get; set; } + public string TimeBase { get; set; } + public string CodecTimeBase { get; set; } + public string NalLengthSize { get; set; } + public bool IsInterlaced { get; set; } + public bool IsAVC { get; set; } + public int BitRate { get; set; } + public int BitDepth { get; set; } + public int RefFrames { get; set; } + public bool IsDefault { get; set; } + public bool IsForced { get; set; } + public int Height { get; set; } + public int Width { get; set; } + public float AverageFrameRate { get; set; } + public float RealFrameRate { get; set; } + public string Profile { get; set; } + public string Type { get; set; } + public string AspectRatio { get; set; } + public int Index { get; set; } + public bool IsExternal { get; set; } + public bool IsTextSubtitleStream { get; set; } + public bool SupportsExternalStream { get; set; } + public string PixelFormat { get; set; } + public int Level { get; set; } + public bool IsAnamorphic { get; set; } + public string DisplayTitle { get; set; } + public string ChannelLayout { get; set; } + public int Channels { get; set; } + public int SampleRate { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyMovieInformation.cs b/Ombi.Api.Models/Emby/EmbyMovieInformation.cs new file mode 100644 index 000000000..bb9bfe244 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyMovieInformation.cs @@ -0,0 +1,87 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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; + +namespace Ombi.Api.Models.Emby +{ + public class EmbyMovieInformation + { + public string Name { get; set; } + public string OriginalTitle { get; set; } + public string ServerId { get; set; } + public string Id { get; set; } + public string Etag { get; set; } + public DateTime DateCreated { get; set; } + public bool CanDelete { get; set; } + public bool CanDownload { get; set; } + public bool SupportsSync { get; set; } + public string Container { get; set; } + public string SortName { get; set; } + public DateTime PremiereDate { get; set; } + public EmbyExternalurl[] ExternalUrls { get; set; } + public EmbyMediasource[] MediaSources { get; set; } + public string[] ProductionLocations { get; set; } + public string Path { get; set; } + public string OfficialRating { get; set; } + public string Overview { get; set; } + public string[] Taglines { get; set; } + public string[] Genres { get; set; } + public float CommunityRating { get; set; } + public int VoteCount { get; set; } + public long RunTimeTicks { get; set; } + public string PlayAccess { get; set; } + public int ProductionYear { get; set; } + public bool IsPlaceHolder { get; set; } + public EmbyRemotetrailer[] RemoteTrailers { get; set; } + public EmbyProviderids ProviderIds { get; set; } + public bool IsHD { get; set; } + public bool IsFolder { get; set; } + public string ParentId { get; set; } + public string Type { get; set; } + public EmbyPerson[] People { get; set; } + public EmbyStudio[] Studios { get; set; } + public int LocalTrailerCount { get; set; } + public EmbyUserdata UserData { get; set; } + public string DisplayPreferencesId { get; set; } + public object[] Tags { get; set; } + public string[] Keywords { get; set; } + public EmbyMediastream[] MediaStreams { get; set; } + public string VideoType { get; set; } + public EmbyImagetags ImageTags { get; set; } + public string[] BackdropImageTags { get; set; } + public object[] ScreenshotImageTags { get; set; } + public EmbyChapter[] Chapters { get; set; } + public string LocationType { get; set; } + public string MediaType { get; set; } + public string HomePageUrl { get; set; } + public int Budget { get; set; } + public int Revenue { get; set; } + public object[] LockedFields { get; set; } + public bool LockData { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyMovieItem.cs b/Ombi.Api.Models/Emby/EmbyMovieItem.cs new file mode 100644 index 000000000..5c4cc514f --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyMovieItem.cs @@ -0,0 +1,59 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyMovieItem.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; + +namespace Ombi.Api.Models.Emby +{ + public class EmbyMovieItem + { + public string Name { get; set; } + public string ServerId { get; set; } + public string Id { get; set; } + public string Container { get; set; } + public DateTime PremiereDate { get; set; } + public object[] ProductionLocations { get; set; } + public string OfficialRating { get; set; } + public float CommunityRating { get; set; } + public long RunTimeTicks { get; set; } + public string PlayAccess { get; set; } + public int ProductionYear { get; set; } + public bool IsPlaceHolder { get; set; } + public bool IsHD { get; set; } + public bool IsFolder { get; set; } + public string Type { get; set; } + public int LocalTrailerCount { get; set; } + public EmbyUserdata UserData { get; set; } + public string VideoType { get; set; } + public EmbyImagetags ImageTags { get; set; } + public string[] BackdropImageTags { get; set; } + public string LocationType { get; set; } + public string MediaType { get; set; } + public bool HasSubtitles { get; set; } + public int CriticRating { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyPerson.cs b/Ombi.Api.Models/Emby/EmbyPerson.cs new file mode 100644 index 000000000..7ae04d1c4 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyPerson.cs @@ -0,0 +1,39 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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.Api.Models.Emby +{ + public class EmbyPerson + { + public string Name { get; set; } + public string Id { get; set; } + public string Role { get; set; } + public string Type { get; set; } + public string PrimaryImageTag { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyPolicy.cs b/Ombi.Api.Models/Emby/EmbyPolicy.cs new file mode 100644 index 000000000..5ffe07bce --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyPolicy.cs @@ -0,0 +1,63 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyUser.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.Api.Models.Emby +{ + public class EmbyPolicy + { + public bool IsAdministrator { get; set; } + public bool IsHidden { get; set; } + public bool IsDisabled { get; set; } + public object[] BlockedTags { get; set; } + public bool EnableUserPreferenceAccess { get; set; } + public object[] AccessSchedules { get; set; } + public object[] BlockUnratedItems { get; set; } + public bool EnableRemoteControlOfOtherUsers { get; set; } + public bool EnableSharedDeviceControl { get; set; } + public bool EnableLiveTvManagement { get; set; } + public bool EnableLiveTvAccess { get; set; } + public bool EnableMediaPlayback { get; set; } + public bool EnableAudioPlaybackTranscoding { get; set; } + public bool EnableVideoPlaybackTranscoding { get; set; } + public bool EnablePlaybackRemuxing { get; set; } + public bool EnableContentDeletion { get; set; } + public bool EnableContentDownloading { get; set; } + public bool EnableSync { get; set; } + public bool EnableSyncTranscoding { get; set; } + public object[] EnabledDevices { get; set; } + public bool EnableAllDevices { get; set; } + public object[] EnabledChannels { get; set; } + public bool EnableAllChannels { get; set; } + public object[] EnabledFolders { get; set; } + public bool EnableAllFolders { get; set; } + public int InvalidLoginAttemptCount { get; set; } + public bool EnablePublicSharing { get; set; } + } + + +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyProviderids.cs b/Ombi.Api.Models/Emby/EmbyProviderids.cs new file mode 100644 index 000000000..d2858850f --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyProviderids.cs @@ -0,0 +1,41 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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.Api.Models.Emby +{ + public class EmbyProviderids + { + public string Tmdb { get; set; } + public string Imdb { get; set; } + public string TmdbCollection { get; set; } + + public string Tvdb { get; set; } + public string Zap2It { get; set; } + public string TvRage { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyRemotetrailer.cs b/Ombi.Api.Models/Emby/EmbyRemotetrailer.cs new file mode 100644 index 000000000..5f2d60923 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyRemotetrailer.cs @@ -0,0 +1,36 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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.Api.Models.Emby +{ + public class EmbyRemotetrailer + { + public string Url { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyRequiredhttpheaders.cs b/Ombi.Api.Models/Emby/EmbyRequiredhttpheaders.cs new file mode 100644 index 000000000..bd2cbef45 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyRequiredhttpheaders.cs @@ -0,0 +1,36 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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.Api.Models.Emby +{ + public class EmbyRequiredhttpheaders + { + } + + +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbySeriesInformation.cs b/Ombi.Api.Models/Emby/EmbySeriesInformation.cs new file mode 100644 index 000000000..7cc8ba9ce --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbySeriesInformation.cs @@ -0,0 +1,83 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbySeriesInformation.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; + +namespace Ombi.Api.Models.Emby +{ + public class EmbySeriesInformation + { + public string Name { get; set; } + public string ServerId { get; set; } + public string Id { get; set; } + public string Etag { get; set; } + public DateTime DateCreated { get; set; } + public DateTime DateLastMediaAdded { get; set; } + public bool CanDelete { get; set; } + public bool CanDownload { get; set; } + public bool SupportsSync { get; set; } + public string SortName { get; set; } + public DateTime PremiereDate { get; set; } + public EmbyExternalurl[] ExternalUrls { get; set; } + public string Path { get; set; } + public string OfficialRating { get; set; } + public string Overview { get; set; } + public string ShortOverview { get; set; } + public object[] Taglines { get; set; } + public string[] Genres { get; set; } + public float CommunityRating { get; set; } + public int VoteCount { get; set; } + public long CumulativeRunTimeTicks { get; set; } + public long RunTimeTicks { get; set; } + public string PlayAccess { get; set; } + public int ProductionYear { get; set; } + public EmbyRemotetrailer[] RemoteTrailers { get; set; } + public EmbyProviderids ProviderIds { get; set; } + public bool IsFolder { get; set; } + public string ParentId { get; set; } + public string Type { get; set; } + public EmbyPerson[] People { get; set; } + public EmbyStudio[] Studios { get; set; } + public int LocalTrailerCount { get; set; } + public EmbyUserdata UserData { get; set; } + public int RecursiveItemCount { get; set; } + public int ChildCount { get; set; } + public string DisplayPreferencesId { get; set; } + public string Status { get; set; } + public string AirTime { get; set; } + public string[] AirDays { get; set; } + public object[] Tags { get; set; } + public object[] Keywords { get; set; } + public EmbyImagetags ImageTags { get; set; } + public string[] BackdropImageTags { get; set; } + public object[] ScreenshotImageTags { get; set; } + public string LocationType { get; set; } + public string HomePageUrl { get; set; } + public object[] LockedFields { get; set; } + public bool LockData { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbySeriesItem.cs b/Ombi.Api.Models/Emby/EmbySeriesItem.cs new file mode 100644 index 000000000..2c3674662 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbySeriesItem.cs @@ -0,0 +1,56 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbySeriesItem.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; + +namespace Ombi.Api.Models.Emby +{ + public class EmbySeriesItem + { + public string Name { get; set; } + public string ServerId { get; set; } + public string Id { get; set; } + public DateTime PremiereDate { get; set; } + public string OfficialRating { get; set; } + public float CommunityRating { get; set; } + public long RunTimeTicks { get; set; } + public string PlayAccess { get; set; } + public int ProductionYear { get; set; } + public bool IsFolder { get; set; } + public string Type { get; set; } + public int LocalTrailerCount { get; set; } + public EmbyUserdata UserData { get; set; } + public int ChildCount { get; set; } + public string Status { get; set; } + public string AirTime { get; set; } + public string[] AirDays { get; set; } + public EmbyImagetags ImageTags { get; set; } + public string[] BackdropImageTags { get; set; } + public string LocationType { get; set; } + public DateTime EndDate { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbySeriesstudioinfo.cs b/Ombi.Api.Models/Emby/EmbySeriesstudioinfo.cs new file mode 100644 index 000000000..8b2ab437d --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbySeriesstudioinfo.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyEpisodeInformation.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.Api.Models.Emby +{ + public class EmbySeriesstudioinfo + { + public string Name { get; set; } + public string Id { get; set; } + } + +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyStudio.cs b/Ombi.Api.Models/Emby/EmbyStudio.cs new file mode 100644 index 000000000..9fa11afe3 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyStudio.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieInformation.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.Api.Models.Emby +{ + public class EmbyStudio + { + public string Name { get; set; } + public string Id { get; set; } + } + +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyUser.cs b/Ombi.Api.Models/Emby/EmbyUser.cs new file mode 100644 index 000000000..f93cd8230 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyUser.cs @@ -0,0 +1,53 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyUser.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; + +namespace Ombi.Api.Models.Emby +{ + public class EmbyUser + { + public string Name { get; set; } + public string ServerId { get; set; } + public string ConnectUserName { get; set; } + public string ConnectUserId { get; set; } + public string ConnectLinkType { get; set; } + public string Id { get; set; } + public bool HasPassword { get; set; } + public bool HasConfiguredPassword { get; set; } + public bool HasConfiguredEasyPassword { get; set; } + public DateTime LastLoginDate { get; set; } + public DateTime LastActivityDate { get; set; } + public EmbyConfiguration Configuration { get; set; } + public EmbyPolicy Policy { get; set; } + } + + public class EmbyUserLogin + { + public EmbyUser User { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbyUserdata.cs b/Ombi.Api.Models/Emby/EmbyUserdata.cs new file mode 100644 index 000000000..fe86591d0 --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbyUserdata.cs @@ -0,0 +1,42 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyLibrary.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; + +namespace Ombi.Api.Models.Emby +{ + public class EmbyUserdata + { + public int PlaybackPositionTicks { get; set; } + public int PlayCount { get; set; } + public bool IsFavorite { get; set; } + public bool Played { get; set; } + public string Key { get; set; } + public DateTime LastPlayedDate { get; set; } + public int UnplayedItemCount { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Ombi.Api.Models.csproj b/Ombi.Api.Models/Ombi.Api.Models.csproj index 39df12460..3d5fc3460 100644 --- a/Ombi.Api.Models/Ombi.Api.Models.csproj +++ b/Ombi.Api.Models/Ombi.Api.Models.csproj @@ -49,6 +49,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ombi.Api/EmbyApi.cs b/Ombi.Api/EmbyApi.cs new file mode 100644 index 000000000..bbd25dadd --- /dev/null +++ b/Ombi.Api/EmbyApi.cs @@ -0,0 +1,232 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyApi.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.Net; +using Newtonsoft.Json; +using NLog; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Emby; +using Ombi.Helpers; +using RestSharp; + +namespace Ombi.Api +{ + public class EmbyApi : IEmbyApi + { + public EmbyApi() + { + Api = new ApiRequest(); + } + + private ApiRequest Api { get; } + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + /// + /// Returns all users from the Emby Instance + /// + /// + /// + public List GetUsers(Uri baseUri, string apiKey) + { + var request = new RestRequest + { + Resource = "emby/users", + Method = Method.GET + }; + + AddHeaders(request, apiKey); + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetUsers for Emby, Retrying {0}", timespan), new[] { + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(5) + }); + + var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUri)); + + return obj; + } + + public EmbyItemContainer ViewLibrary(string apiKey, string userId, Uri baseUri) + { + var request = new RestRequest + { + Resource = "emby/users/{userId}/items", + Method = Method.GET + }; + + request.AddUrlSegment("userId", userId); + AddHeaders(request, apiKey); + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling ViewLibrary for Emby, Retrying {0}", timespan), new[] { + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(5) + }); + + var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUri)); + + return obj; + } + + public EmbyItemContainer GetAllMovies(string apiKey, string userId, Uri baseUri) + { + return GetAll("Movie", apiKey, userId, baseUri); + } + + public EmbyItemContainer GetAllEpisodes(string apiKey, string userId, Uri baseUri) + { + return GetAll("Episode", apiKey, userId, baseUri); + } + + public EmbyInformation GetInformation(string mediaId, EmbyMediaType type, string apiKey, string userId, Uri baseUri) + { + var request = new RestRequest + { + Resource = "emby/users/{userId}/items/{mediaId}", + Method = Method.GET + }; + + request.AddUrlSegment("userId", userId); + request.AddUrlSegment("mediaId", mediaId); + + AddHeaders(request, apiKey); + + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetAll({1}) for Emby, Retrying {0}", timespan, type), new[] { + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(5) + }); + + switch (type) + { + case EmbyMediaType.Movie: + return new EmbyInformation + { + MovieInformation = policy.Execute(() => Api.ExecuteJson(request, baseUri)) + }; + case EmbyMediaType.Series: + return new EmbyInformation + { + SeriesInformation = + policy.Execute(() => Api.ExecuteJson(request, baseUri)) + }; + case EmbyMediaType.Music: + break; + case EmbyMediaType.Episode: + return new EmbyInformation + { + EpisodeInformation = + policy.Execute(() => Api.ExecuteJson(request, baseUri)) + }; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + return new EmbyInformation(); + } + + + public EmbyItemContainer GetAllShows(string apiKey, string userId, Uri baseUri) + { + return GetAll("Series", apiKey, userId, baseUri); + } + + public EmbyUser LogIn(string username, string password, string apiKey, Uri baseUri) + { + var request = new RestRequest + { + Resource = "emby/users/authenticatebyname", + Method = Method.POST + }; + + var body = new + { + username, + password = StringHasher.GetSha1Hash(password).ToLower(), + passwordMd5 = StringHasher.CalcuateMd5Hash(password) + }; + + request.AddJsonBody(body); + + request.AddHeader("X-Emby-Authorization", + $"MediaBrowser Client=\"Ombi\", Device=\"Ombi\", DeviceId=\"{AssemblyHelper.GetProductVersion()}\", Version=\"{AssemblyHelper.GetAssemblyVersion()}\""); + AddHeaders(request, apiKey); + + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling LogInfor Emby, Retrying {0}", timespan), new[] { + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(5) + }); + + var obj = policy.Execute(() => Api.Execute(request, baseUri)); + + if (obj.StatusCode == HttpStatusCode.Unauthorized) + { + return null; + } + + return JsonConvert.DeserializeObject(obj.Content)?.User; + } + + private EmbyItemContainer GetAll(string type, string apiKey, string userId, Uri baseUri) + { + var request = new RestRequest + { + Resource = "emby/users/{userId}/items", + Method = Method.GET + }; + + request.AddUrlSegment("userId", userId); + request.AddQueryParameter("Recursive", true.ToString()); + request.AddQueryParameter("IncludeItemTypes", type); + + AddHeaders(request, apiKey); + + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetAll({1}) for Emby, Retrying {0}", timespan, type), new[] { + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(5) + }); + + var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUri)); + + return obj; + } + + + private static void AddHeaders(IRestRequest req, string apiKey) + { + if (!string.IsNullOrEmpty(apiKey)) + { + req.AddHeader("X-MediaBrowser-Token", apiKey); + } + req.AddHeader("Accept", "application/json"); + req.AddHeader("Content-Type", "application/json"); + req.AddHeader("Device", "Ombi"); + } + } +} \ No newline at end of file diff --git a/Ombi.Api/Ombi.Api.csproj b/Ombi.Api/Ombi.Api.csproj index b9e674cc2..c9d00b030 100644 --- a/Ombi.Api/Ombi.Api.csproj +++ b/Ombi.Api/Ombi.Api.csproj @@ -70,10 +70,14 @@ ..\packages\TraktApiSharp.0.8.0\lib\portable-net45+netcore45+wpa81\TraktApiSharp.dll True + + ..\packages\WebSocket4Net.0.14.1\lib\net45\WebSocket4Net.dll + + diff --git a/Ombi.Api/packages.config b/Ombi.Api/packages.config index a20220585..b21cb47a8 100644 --- a/Ombi.Api/packages.config +++ b/Ombi.Api/packages.config @@ -9,4 +9,5 @@ + \ No newline at end of file diff --git a/Ombi.Core.Migration/Migrations/Version1100.cs b/Ombi.Core.Migration/Migrations/Version1100.cs index 55d537758..4c184ca22 100644 --- a/Ombi.Core.Migration/Migrations/Version1100.cs +++ b/Ombi.Core.Migration/Migrations/Version1100.cs @@ -37,6 +37,7 @@ using Ombi.Helpers; using Ombi.Helpers.Permissions; using Ombi.Store; using Ombi.Store.Models; +using Ombi.Store.Models.Plex; using Ombi.Store.Repository; namespace Ombi.Core.Migration.Migrations @@ -46,7 +47,7 @@ namespace Ombi.Core.Migration.Migrations { public Version1100(IUserRepository userRepo, IRequestService requestService, ISettingsService log, IPlexApi plexApi, ISettingsService plexService, - IPlexUserRepository plexusers, ISettingsService prSettings, + IExternalUserRepository plexusers, ISettingsService prSettings, ISettingsService umSettings, ISettingsService sjs, IRepository usersToNotify) { @@ -69,7 +70,7 @@ namespace Ombi.Core.Migration.Migrations private ISettingsService Log { get; } private IPlexApi PlexApi { get; } private ISettingsService PlexSettings { get; } - private IPlexUserRepository PlexUsers { get; } + private IExternalUserRepository PlexUsers { get; } private ISettingsService PlexRequestSettings { get; } private ISettingsService UserManagementSettings { get; } private ISettingsService ScheduledJobSettings { get; } diff --git a/Ombi.Core.Migration/Migrations/Version2200.cs b/Ombi.Core.Migration/Migrations/Version2200.cs index a28f852de..ac9b5d1a7 100644 --- a/Ombi.Core.Migration/Migrations/Version2200.cs +++ b/Ombi.Core.Migration/Migrations/Version2200.cs @@ -36,29 +36,42 @@ namespace Ombi.Core.Migration.Migrations [Migration(22000, "v2.20.0.0")] public class Version2200 : BaseMigration, IMigration { - public Version2200(ISettingsService custom) + public Version2200(ISettingsService custom, ISettingsService ps) { Customization = custom; + PlexSettings = ps; } public int Version => 22000; private ISettingsService Customization { get; set; } + private ISettingsService PlexSettings { get; set; } private static Logger Logger = LogManager.GetCurrentClassLogger(); public void Start(IDbConnection con) { + UpdatePlexSettings(); //UpdateCustomSettings(); Turned off the migration for now until the search has been improved on. //UpdateSchema(con, Version); } + private void UpdatePlexSettings() + { +#if !DEBUG + var s = PlexSettings.GetSettings(); + s.Enable = true; + PlexSettings.SaveSettings(s); +#endif + } private void UpdateCustomSettings() { + var settings = Customization.GetSettings(); settings.NewSearch = true; // Use the new search Customization.SaveSettings(settings); + } } } diff --git a/Ombi.Core/ISecurityExtensions.cs b/Ombi.Core/ISecurityExtensions.cs index 0743b5a51..056d171a9 100644 --- a/Ombi.Core/ISecurityExtensions.cs +++ b/Ombi.Core/ISecurityExtensions.cs @@ -22,7 +22,7 @@ namespace Ombi.Core Func HttpStatusCodeIfNot(HttpStatusCode statusCode, Func test); bool IsLoggedIn(NancyContext context); bool IsNormalUser(IUserIdentity user); - bool IsPlexUser(IUserIdentity user); + bool IsExternalUser(IUserIdentity user); bool HasPermissions(string userName, Permissions perm); /// diff --git a/Ombi.Core/Ombi.Core.csproj b/Ombi.Core/Ombi.Core.csproj index f3f05b9da..9866e6fb0 100644 --- a/Ombi.Core/Ombi.Core.csproj +++ b/Ombi.Core/Ombi.Core.csproj @@ -123,6 +123,7 @@ + diff --git a/Ombi.Core/Queue/TransientFaultQueue.cs b/Ombi.Core/Queue/TransientFaultQueue.cs index 11eb1bd5a..fcb8338d4 100644 --- a/Ombi.Core/Queue/TransientFaultQueue.cs +++ b/Ombi.Core/Queue/TransientFaultQueue.cs @@ -105,7 +105,7 @@ namespace Ombi.Core.Queue public IEnumerable GetQueue() { var items = RequestQueue.GetAll(); - + return items; } diff --git a/Ombi.Core/SecurityExtensions.cs b/Ombi.Core/SecurityExtensions.cs index 7a244554a..326b0b448 100644 --- a/Ombi.Core/SecurityExtensions.cs +++ b/Ombi.Core/SecurityExtensions.cs @@ -36,23 +36,28 @@ using Ombi.Core.SettingModels; using Ombi.Core.Users; using Ombi.Helpers; using Ombi.Helpers.Permissions; +using Ombi.Store.Models.Emby; +using Ombi.Store.Models.Plex; using Ombi.Store.Repository; namespace Ombi.Core { public class SecurityExtensions : ISecurityExtensions { - public SecurityExtensions(IUserRepository userRepository, IResourceLinker linker, IPlexUserRepository plexUsers, ISettingsService umSettings) + public SecurityExtensions(IUserRepository userRepository, IResourceLinker linker, IExternalUserRepository plexUsers, ISettingsService umSettings, + IExternalUserRepository embyUsers) { UserRepository = userRepository; Linker = linker; PlexUsers = plexUsers; UserManagementSettings = umSettings; + EmbyUsers = embyUsers; } private IUserRepository UserRepository { get; } private IResourceLinker Linker { get; } - private IPlexUserRepository PlexUsers { get; } + private IExternalUserRepository PlexUsers { get; } + private IExternalUserRepository EmbyUsers { get; } private ISettingsService UserManagementSettings { get; } public bool IsLoggedIn(NancyContext context) @@ -69,16 +74,18 @@ namespace Ombi.Core return realUser || plexUser; } - public bool IsPlexUser(IUserIdentity user) + public bool IsExternalUser(IUserIdentity user) { if (user == null) { return false; } var plexUser = PlexUsers.GetUserByUsername(user.UserName); - return plexUser != null; - } + var embyUser = EmbyUsers.GetUserByUsername(user.UserName); + return plexUser != null || embyUser != null; + } + public bool IsNormalUser(IUserIdentity user) { if (user == null) @@ -106,6 +113,12 @@ namespace Ombi.Core return !string.IsNullOrEmpty(plexUser.UserAlias) ? plexUser.UserAlias : plexUser.Username; } + var embyUser = EmbyUsers.GetUserByUsername(username); + if (embyUser != null) + { + return !string.IsNullOrEmpty(embyUser.UserAlias) ? embyUser.UserAlias : embyUser.Username; + } + var dbUser = UserRepository.GetUserByUsername(username); if (dbUser != null) { @@ -302,6 +315,12 @@ namespace Ombi.Core return permissions; } + var embyUsers = EmbyUsers.GetUserByUsername(userName); + if (embyUsers != null) + { + return (Permissions) embyUsers.Permissions; + } + return 0; } } diff --git a/Ombi.Core/SettingModels/EmbySettings.cs b/Ombi.Core/SettingModels/EmbySettings.cs new file mode 100644 index 000000000..42344cddd --- /dev/null +++ b/Ombi.Core/SettingModels/EmbySettings.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CouchPotatoSettings.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.Core.SettingModels +{ + public sealed class EmbySettings : ExternalSettings + { + public bool Enable { get; set; } + public string ApiKey { get; set; } + public string AdministratorId { get; set; } + public bool EnableEpisodeSearching { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Core/SettingModels/PlexSettings.cs b/Ombi.Core/SettingModels/PlexSettings.cs index b40b69018..91f95fe2e 100644 --- a/Ombi.Core/SettingModels/PlexSettings.cs +++ b/Ombi.Core/SettingModels/PlexSettings.cs @@ -33,6 +33,8 @@ namespace Ombi.Core.SettingModels { AdvancedSearch = true; } + + public bool Enable { get; set; } public bool AdvancedSearch { get; set; } public bool EnableTvEpisodeSearching { get; set; } diff --git a/Ombi.Core/SettingModels/ScheduledJobsSettings.cs b/Ombi.Core/SettingModels/ScheduledJobsSettings.cs index a2609206b..b6f7e68d7 100644 --- a/Ombi.Core/SettingModels/ScheduledJobsSettings.cs +++ b/Ombi.Core/SettingModels/ScheduledJobsSettings.cs @@ -47,5 +47,10 @@ namespace Ombi.Core.SettingModels public int PlexContentCacher { get; set; } public int PlexUserChecker { get; set; } public int RadarrCacher { get; set; } + + public int EmbyEpisodeCacher { get; set; } + public int EmbyContentCacher { get; set; } + public int EmbyAvailabilityChecker { get; set; } + public int EmbyUserChecker { get; set; } } } \ No newline at end of file diff --git a/Ombi.Core/Users/UserHelper.cs b/Ombi.Core/Users/UserHelper.cs index 099b025b8..ae3174de1 100644 --- a/Ombi.Core/Users/UserHelper.cs +++ b/Ombi.Core/Users/UserHelper.cs @@ -30,22 +30,26 @@ using System.Linq; using Ombi.Core.Models; using Ombi.Helpers; using Ombi.Helpers.Permissions; +using Ombi.Store.Models.Emby; +using Ombi.Store.Models.Plex; using Ombi.Store.Repository; namespace Ombi.Core.Users { public class UserHelper : IUserHelper { - public UserHelper(IUserRepository userRepository, IPlexUserRepository plexUsers, ISecurityExtensions security) + public UserHelper(IUserRepository userRepository, IExternalUserRepository plexUsers, IExternalUserRepository emby, ISecurityExtensions security) { LocalUserRepository = userRepository; PlexUserRepository = plexUsers; Security = security; + EmbyUserRepository = emby; } private IUserRepository LocalUserRepository { get; } - private IPlexUserRepository PlexUserRepository { get; } + private IExternalUserRepository PlexUserRepository { get; } private ISecurityExtensions Security { get; } + private IExternalUserRepository EmbyUserRepository { get; } public IEnumerable GetUsers() @@ -53,7 +57,8 @@ namespace Ombi.Core.Users var model = new List(); var localUsers = LocalUserRepository.GetAll(); - var plexUsers = PlexUserRepository.GetAll(); + var plexUsers = PlexUserRepository.GetAll().ToList(); + var embyUsers = EmbyUserRepository.GetAll().ToList(); foreach (var user in localUsers) { @@ -68,14 +73,30 @@ namespace Ombi.Core.Users }); } - model.AddRange(plexUsers.Select(user => new UserHelperModel + if (plexUsers.Any()) { - Type = UserType.LocalUser, - Username = user.Username, - UserAlias = user.UserAlias, - EmailAddress = user.EmailAddress, - Permissions = (Permissions)user.Permissions - })); + model.AddRange(plexUsers.Select(user => new UserHelperModel + { + Type = UserType.PlexUser, + Username = user.Username, + UserAlias = user.UserAlias, + EmailAddress = user.EmailAddress, + Permissions = (Permissions) user.Permissions + })); + } + + if (embyUsers.Any()) + { + model.AddRange(embyUsers.Select(user => new UserHelperModel + { + Type = UserType.EmbyUser, + Username = user.Username, + UserAlias = user.UserAlias, + EmailAddress = user.EmailAddress, + Permissions = (Permissions)user.Permissions + })); + + } return model; } @@ -86,9 +107,11 @@ namespace Ombi.Core.Users var localUsers = LocalUserRepository.GetAll().ToList(); var plexUsers = PlexUserRepository.GetAll().ToList(); + var embyUsers = EmbyUserRepository.GetAll().ToList(); var filteredLocal = localUsers.Where(x => ((Permissions)x.Permissions).HasFlag(permission)); var filteredPlex = plexUsers.Where(x => ((Permissions)x.Permissions).HasFlag(permission)); + var filteredEmby = embyUsers.Where(x => ((Permissions)x.Permissions).HasFlag(permission)); foreach (var user in filteredLocal) @@ -107,7 +130,17 @@ namespace Ombi.Core.Users model.AddRange(filteredPlex.Select(user => new UserHelperModel { - Type = UserType.LocalUser, + Type = UserType.PlexUser, + Username = user.Username, + UserAlias = user.UserAlias, + EmailAddress = user.EmailAddress, + Permissions = (Permissions)user.Permissions, + Features = (Features)user.Features + })); + + model.AddRange(filteredEmby.Select(user => new UserHelperModel + { + Type = UserType.EmbyUser, Username = user.Username, UserAlias = user.UserAlias, EmailAddress = user.EmailAddress, @@ -115,6 +148,7 @@ namespace Ombi.Core.Users Features = (Features)user.Features })); + return model; } @@ -124,9 +158,11 @@ namespace Ombi.Core.Users var localUsers = LocalUserRepository.GetAll().ToList(); var plexUsers = PlexUserRepository.GetAll().ToList(); + var embyUsers = PlexUserRepository.GetAll().ToList(); var filteredLocal = localUsers.Where(x => ((Features)x.Features).HasFlag(features)); var filteredPlex = plexUsers.Where(x => ((Features)x.Features).HasFlag(features)); + var filteredEmby = embyUsers.Where(x => ((Features)x.Features).HasFlag(features)); foreach (var user in filteredLocal) @@ -145,7 +181,17 @@ namespace Ombi.Core.Users model.AddRange(filteredPlex.Select(user => new UserHelperModel { - Type = UserType.LocalUser, + Type = UserType.PlexUser, + Username = user.Username, + UserAlias = user.UserAlias, + EmailAddress = user.EmailAddress, + Permissions = (Permissions)user.Permissions, + Features = (Features)user.Features + })); + + model.AddRange(filteredEmby.Select(user => new UserHelperModel + { + Type = UserType.EmbyUser, Username = user.Username, UserAlias = user.UserAlias, EmailAddress = user.EmailAddress, diff --git a/Ombi.Helpers/StringHasher.cs b/Ombi.Helpers/StringHasher.cs index 319eeb392..40228616c 100644 --- a/Ombi.Helpers/StringHasher.cs +++ b/Ombi.Helpers/StringHasher.cs @@ -25,6 +25,7 @@ // ************************************************************************/ #endregion +using System.Linq; using System.Security.Cryptography; using System.Text; @@ -49,5 +50,10 @@ namespace Ombi.Helpers return sb.ToString(); } } + + public static string GetSha1Hash(string input) + { + return string.Join("", (new SHA1Managed().ComputeHash(Encoding.UTF8.GetBytes(input))).Select(x => x.ToString("x2")).ToArray()); + } } } \ No newline at end of file diff --git a/Ombi.Helpers/UserType.cs b/Ombi.Helpers/UserType.cs index 30c4a492c..7efd3892c 100644 --- a/Ombi.Helpers/UserType.cs +++ b/Ombi.Helpers/UserType.cs @@ -30,6 +30,7 @@ namespace Ombi.Helpers public enum UserType { PlexUser, - LocalUser + LocalUser, + EmbyUser } } \ No newline at end of file diff --git a/Ombi.Services.Tests/PlexAvailabilityCheckerTests.cs b/Ombi.Services.Tests/PlexAvailabilityCheckerTests.cs index 1097bfb67..47b9e5007 100644 --- a/Ombi.Services.Tests/PlexAvailabilityCheckerTests.cs +++ b/Ombi.Services.Tests/PlexAvailabilityCheckerTests.cs @@ -249,7 +249,7 @@ // }); // CacheMock.Setup(x => x.Get>(CacheKeys.PlexLibaries)).Returns(cachedMovies); // SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create()); -// var movies = Checker.GetPlexMovies(); +// var movies = Checker.GetEmbyMovies(); // Assert.That(movies.Any(x => x.ProviderId == "1212")); // } @@ -267,7 +267,7 @@ // }); // SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create()); // CacheMock.Setup(x => x.Get>(CacheKeys.PlexLibaries)).Returns(cachedTv); -// var movies = Checker.GetPlexTvShows(); +// var movies = Checker.GetEmbyTvShows(); // Assert.That(movies.Any(x => x.ProviderId == "1212")); // } diff --git a/Ombi.Services/Interfaces/IEmbyNotificationEngine.cs b/Ombi.Services/Interfaces/IEmbyNotificationEngine.cs new file mode 100644 index 000000000..c96ebd95b --- /dev/null +++ b/Ombi.Services/Interfaces/IEmbyNotificationEngine.cs @@ -0,0 +1,6 @@ +namespace Ombi.Services.Interfaces +{ + public interface IEmbyNotificationEngine : INotificationEngine + { + } +} \ No newline at end of file diff --git a/Ombi.Services/Interfaces/INotificationEngine.cs b/Ombi.Services/Interfaces/INotificationEngine.cs index bfca46dfa..c2f2a138f 100644 --- a/Ombi.Services/Interfaces/INotificationEngine.cs +++ b/Ombi.Services/Interfaces/INotificationEngine.cs @@ -34,7 +34,7 @@ namespace Ombi.Services.Interfaces { public interface INotificationEngine { - Task NotifyUsers(IEnumerable modelChanged, string apiKey, NotificationType type); - Task NotifyUsers(RequestedModel modelChanged, string apiKey, NotificationType type); + Task NotifyUsers(IEnumerable modelChanged, NotificationType type); + Task NotifyUsers(RequestedModel modelChanged, NotificationType type); } } \ No newline at end of file diff --git a/Ombi.Services/Interfaces/IPlexNotificationEngine.cs b/Ombi.Services/Interfaces/IPlexNotificationEngine.cs new file mode 100644 index 000000000..019668d60 --- /dev/null +++ b/Ombi.Services/Interfaces/IPlexNotificationEngine.cs @@ -0,0 +1,6 @@ +namespace Ombi.Services.Interfaces +{ + public interface IPlexNotificationEngine : INotificationEngine + { + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/EmbyAvailabilityChecker.cs b/Ombi.Services/Jobs/EmbyAvailabilityChecker.cs new file mode 100644 index 000000000..1223e3eb8 --- /dev/null +++ b/Ombi.Services/Jobs/EmbyAvailabilityChecker.cs @@ -0,0 +1,357 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexAvailabilityChecker.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.Threading.Tasks; +using Dapper; +using NLog; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Plex; +using Ombi.Core; +using Ombi.Core.Models; +using Ombi.Core.SettingModels; +using Ombi.Helpers; +using Ombi.Services.Interfaces; +using Ombi.Services.Models; +using Ombi.Store; +using Ombi.Store.Models; +using Ombi.Store.Models.Emby; +using Ombi.Store.Models.Plex; +using Ombi.Store.Repository; +using Quartz; +using PlexMediaType = Ombi.Api.Models.Plex.PlexMediaType; + +namespace Ombi.Services.Jobs +{ + public class EmbyAvailabilityChecker : IJob, IEmbyAvailabilityChecker + { + public EmbyAvailabilityChecker(ISettingsService embySettings, IRequestService request, IEmbyApi emby, ICacheProvider cache, + INotificationService notify, IJobRecord rec, IRepository users, IRepository repo, IEmbyNotificationEngine e, IRepository content) + { + Emby = embySettings; + RequestService = request; + EmbyApi = emby; + Cache = cache; + Notification = notify; + Job = rec; + UserNotifyRepo = users; + EpisodeRepo = repo; + NotificationEngine = e; + EmbyContent = content; + } + + private ISettingsService Emby { get; } + private IRepository EpisodeRepo { get; } + private IRequestService RequestService { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + private IEmbyApi EmbyApi { get; } + private ICacheProvider Cache { get; } + private INotificationService Notification { get; } + private IJobRecord Job { get; } + private IRepository UserNotifyRepo { get; } + private INotificationEngine NotificationEngine { get; } + private IRepository EmbyContent { get; } + + public void CheckAndUpdateAll() + { + var embySettings = Emby.GetSettings(); + + if (!ValidateSettings(embySettings)) + { + Log.Debug("Validation of the Emby settings failed."); + return; + } + + var content = EmbyContent.GetAll().ToList(); + + var movies = GetEmbyMovies(content).ToArray(); + var shows = GetEmbyTvShows(content).ToArray(); + var albums = GetEmbyMusic(content).ToArray(); + + var requests = RequestService.GetAll(); + var requestedModels = requests as RequestedModel[] ?? requests.Where(x => !x.Available).ToArray(); + + if (!requestedModels.Any()) + { + Log.Debug("There are no requests to check."); + return; + } + + var modifiedModel = new List(); + foreach (var r in requestedModels) + { + var releaseDate = r.ReleaseDate == DateTime.MinValue ? string.Empty : r.ReleaseDate.ToString("yyyy"); + bool matchResult; + + switch (r.Type) + { + case RequestType.Movie: + matchResult = IsMovieAvailable(movies, r.Title, releaseDate, r.ImdbId); + break; + case RequestType.TvShow: + if (!embySettings.EnableEpisodeSearching) + { + matchResult = IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList); + } + else + { + matchResult = r.Episodes.Any() ? + r.Episodes.All(x => IsEpisodeAvailable(r.TvDbId, x.SeasonNumber, x.EpisodeNumber)) : + IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList); + } + break; + case RequestType.Album: + //matchResult = IsAlbumAvailable(albums, r.Title, r.ReleaseDate.Year.ToString(), r.ArtistName); // TODO Emby + matchResult = false; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + + if (matchResult) + { + r.Available = true; + modifiedModel.Add(r); + continue; + } + + } + + Log.Debug("Requests that will be updated count {0}", modifiedModel.Count); + + if (modifiedModel.Any()) + { + NotificationEngine.NotifyUsers(modifiedModel, NotificationType.RequestAvailable); + RequestService.BatchUpdate(modifiedModel); + } + } + + public IEnumerable GetEmbyMovies(IEnumerable content) + { + return content.Where(x => x.Type == EmbyMediaType.Movie); + } + + public bool IsMovieAvailable(EmbyContent[] embyMovies, string title, string year, string providerId) + { + var movie = GetMovie(embyMovies, title, year, providerId); + return movie != null; + } + + public EmbyContent GetMovie(EmbyContent[] embyMovies, string title, string year, string providerId) + { + if (embyMovies.Length == 0) + { + return null; + } + foreach (var movie in embyMovies) + { + if (string.IsNullOrEmpty(movie.Title) || movie.PremierDate == DateTime.MinValue) + { + continue; + } + + if (!string.IsNullOrEmpty(movie.ProviderId) && + movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) + { + return movie; + } + + if (movie.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && + movie.PremierDate.Year.ToString().Equals(year, StringComparison.CurrentCultureIgnoreCase)) + { + return movie; + } + } + return null; + } + + public IEnumerable GetEmbyTvShows(IEnumerable content) + { + return content.Where(x => x.Type == EmbyMediaType.Series); + } + + public bool IsTvShowAvailable(EmbyContent[] embyShows, string title, string year, string providerId, int[] seasons = null) + { + var show = GetTvShow(embyShows, title, year, providerId, seasons); + return show != null; + } + + + public EmbyContent GetTvShow(EmbyContent[] embyShows, string title, string year, string providerId, + int[] seasons = null) + { + foreach (var show in embyShows) + { + //if (show.ProviderId == providerId && seasons != null) // TODO Emby + //{ + // var showSeasons = ByteConverterHelper.ReturnObject(show.Seasons); + // if (seasons.Any(season => showSeasons.Contains(season))) + // { + // return show; + // } + // return null; + //} + if (!string.IsNullOrEmpty(show.ProviderId) && + show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) + { + return show; + } + + if (show.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && + show.PremierDate.Year.ToString().Equals(year, StringComparison.CurrentCultureIgnoreCase)) + { + return show; + } + } + return null; + } + + public bool IsEpisodeAvailable(string theTvDbId, int season, int episode) + { + var ep = EpisodeRepo.Custom( + connection => + { + connection.Open(); + var result = connection.Query("select * from EmbyEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId }); + + return result; + }).ToList(); + + if (!ep.Any()) + { + Log.Info("Episode cache info is not available. tvdbid: {0}, season: {1}, episode: {2}", theTvDbId, season, episode); + return false; + } + foreach (var result in ep) + { + if (result.ProviderId.Equals(theTvDbId) && result.EpisodeNumber == episode && result.SeasonNumber == season) + { + return true; + } + } + return false; + } + + /// + /// Gets the episode's db in the cache. + /// + /// + public async Task> GetEpisodes() + { + var episodes = await EpisodeRepo.GetAllAsync(); + if (episodes == null) + { + return new HashSet(); + } + return episodes; + } + + /// + /// Gets the episode's stored in the db and then filters on the TheTvDBId. + /// + /// The tv database identifier. + /// + public async Task> GetEpisodes(int theTvDbId) + { + var ep = await EpisodeRepo.CustomAsync(async connection => + { + connection.Open(); + var result = await connection.QueryAsync("select * from EmbyEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId }); + + return result; + }); + + var embyEpisodes = ep as EmbyEpisodes[] ?? ep.ToArray(); + if (!embyEpisodes.Any()) + { + Log.Info("Episode db info is not available."); + return new List(); + } + + return embyEpisodes; + } + + public IEnumerable GetEmbyMusic(IEnumerable content) + { + return content.Where(x => x.Type == EmbyMediaType.Music); + } + + + private bool ValidateSettings(EmbySettings emby) + { + if (emby.Enable) + { + if (string.IsNullOrEmpty(emby?.Ip) || string.IsNullOrEmpty(emby?.ApiKey) || string.IsNullOrEmpty(emby?.AdministratorId)) + { + Log.Warn("A setting is null, Ensure Emby is configured correctly"); + return false; + } + } + return emby.Enable; + } + + public void Execute(IJobExecutionContext context) + { + + Job.SetRunning(true, JobNames.EmbyChecker); + try + { + CheckAndUpdateAll(); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + Job.Record(JobNames.EmbyChecker); + Job.SetRunning(false, JobNames.EmbyChecker); + } + } + + public void Start() + { + Job.SetRunning(true, JobNames.EmbyChecker); + try + { + CheckAndUpdateAll(); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + Job.Record(JobNames.EmbyChecker); + Job.SetRunning(false, JobNames.EmbyChecker); + } + } + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/EmbyContentCacher.cs b/Ombi.Services/Jobs/EmbyContentCacher.cs new file mode 100644 index 000000000..ac1ba7bd2 --- /dev/null +++ b/Ombi.Services/Jobs/EmbyContentCacher.cs @@ -0,0 +1,250 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexAvailabilityChecker.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 Dapper; +using NLog; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Emby; +using Ombi.Core; +using Ombi.Core.SettingModels; +using Ombi.Helpers; +using Ombi.Services.Interfaces; +using Ombi.Services.Jobs.Interfaces; +using Ombi.Store.Models.Emby; +using Ombi.Store.Repository; +using Quartz; +using EmbyMediaType = Ombi.Api.Models.Emby.EmbyMediaType; + +namespace Ombi.Services.Jobs +{ + public class EmbyContentCacher : IJob, IEmbyContentCacher + { + public EmbyContentCacher(ISettingsService embySettings, IRequestService request, IEmbyApi emby, ICacheProvider cache, + IJobRecord rec, IRepository repo,IRepository content) + { + Emby = embySettings; + RequestService = request; + EmbyApi = emby; + Cache = cache; + Job = rec; + EpisodeRepo = repo; + EmbyContent = content; + } + + private ISettingsService Emby { get; } + private IRepository EpisodeRepo { get; } + private IRequestService RequestService { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + private IEmbyApi EmbyApi { get; } + private ICacheProvider Cache { get; } + private IJobRecord Job { get; } + private IRepository EmbyContent { get; } + + public void CacheContent() + { + var embySettings = Emby.GetSettings(); + + if (!ValidateSettings(embySettings)) + { + Log.Debug("Validation of emby settings failed."); + return; + } + CachedLibraries(embySettings); + } + + + public List GetMovies() + { + var settings = Emby.GetSettings(); + return EmbyApi.GetAllMovies(settings.ApiKey, settings.AdministratorId, settings.FullUri).Items; + } + + public List GetTvShows() + { + var settings = Emby.GetSettings(); + return EmbyApi.GetAllShows(settings.ApiKey, settings.AdministratorId, settings.FullUri).Items; + } + + private void CachedLibraries(EmbySettings embySettings) + { + + if (!ValidateSettings(embySettings)) + { + Log.Warn("The settings are not configured"); + } + + try + { + var movies = GetMovies(); + + foreach (var m in movies) + { + var movieInfo = EmbyApi.GetInformation(m.Id, EmbyMediaType.Movie, embySettings.ApiKey, + embySettings.AdministratorId, embySettings.FullUri).MovieInformation; + + if (string.IsNullOrEmpty(movieInfo.ProviderIds.Imdb)) + { + Log.Error("Provider Id on movie {0} is null", movieInfo.Name); + continue; + } + + // Check if it exists + var item = EmbyContent.Custom(connection => + { + connection.Open(); + var media = connection.QueryFirstOrDefault("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { ProviderId = movieInfo.ProviderIds.Imdb, type = 0 }); + connection.Dispose(); + return media; + }); + + if (item == null) + { + // Doesn't exist, insert it + EmbyContent.Insert(new EmbyContent + { + ProviderId = movieInfo.ProviderIds.Imdb, + PremierDate = movieInfo.PremiereDate, + Title = movieInfo.Name, + Type = Store.Models.Plex.EmbyMediaType.Movie, + EmbyId = m.Id + }); + } + } + + var tv = GetTvShows(); + + foreach (var t in tv) + { + var tvInfo = EmbyApi.GetInformation(t.Id, EmbyMediaType.Series, embySettings.ApiKey, + embySettings.AdministratorId, embySettings.FullUri).SeriesInformation; + if (string.IsNullOrEmpty(tvInfo.ProviderIds?.Tvdb)) + { + Log.Error("Provider Id on tv {0} is null", t.Name); + continue; + } + + + // Check if it exists + var item = EmbyContent.Custom(connection => + { + connection.Open(); + var media = connection.QueryFirstOrDefault("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { ProviderId = tvInfo.ProviderIds.Tvdb, type = 1 }); + connection.Dispose(); + return media; + }); + + if (item == null) + { + EmbyContent.Insert(new EmbyContent + { + ProviderId = tvInfo.ProviderIds.Tvdb, + PremierDate = tvInfo.PremiereDate, + Title = tvInfo.Name, + Type = Store.Models.Plex.EmbyMediaType.Series, + EmbyId = t.Id + }); + } + } + + //TODO Emby + //var albums = GetPlexAlbums(results); + //foreach (var a in albums) + //{ + // if (string.IsNullOrEmpty(a.ProviderId)) + // { + // Log.Error("Provider Id on album {0} is null", a.Title); + // continue; + // } + + + // // Check if it exists + // var item = EmbyContent.Custom(connection => + // { + // connection.Open(); + // var media = connection.QueryFirstOrDefault("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { a.ProviderId, type = 2 }); + // connection.Dispose(); + // return media; + // }); + + // if (item == null) + // { + + // EmbyContent.Insert(new PlexContent + // { + // ProviderId = a.ProviderId, + // ReleaseYear = a.ReleaseYear ?? string.Empty, + // Title = a.Title, + // Type = Store.Models.Plex.PlexMediaType.Artist, + // Url = a.Url + // }); + // } + //} + + } + catch (Exception ex) + { + Log.Error(ex, "Failed to obtain Emby libraries"); + } + } + + + + private bool ValidateSettings(EmbySettings emby) + { + if (emby.Enable) + { + if (emby?.Ip == null || string.IsNullOrEmpty(emby?.ApiKey)) + { + Log.Warn("A setting is null, Ensure Emby is configured correctly, and we have a Emby Auth token."); + return false; + } + } + return emby.Enable; + } + + public void Execute(IJobExecutionContext context) + { + + Job.SetRunning(true, JobNames.EmbyCacher); + try + { + CacheContent(); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + Job.Record(JobNames.EmbyCacher); + Job.SetRunning(false, JobNames.EmbyCacher); + } + } + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/EmbyEpisodeCacher.cs b/Ombi.Services/Jobs/EmbyEpisodeCacher.cs new file mode 100644 index 000000000..945908149 --- /dev/null +++ b/Ombi.Services/Jobs/EmbyEpisodeCacher.cs @@ -0,0 +1,167 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexEpisodeCacher.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 NLog; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Emby; +using Ombi.Core; +using Ombi.Core.SettingModels; +using Ombi.Helpers; +using Ombi.Services.Interfaces; +using Ombi.Services.Jobs.Interfaces; +using Ombi.Store.Models.Emby; +using Ombi.Store.Repository; +using Quartz; + +namespace Ombi.Services.Jobs +{ + public class EmbyEpisodeCacher : IJob, IEmbyEpisodeCacher + { + public EmbyEpisodeCacher(ISettingsService embySettings, IEmbyApi emby, ICacheProvider cache, + IJobRecord rec, IRepository repo, ISettingsService jobs) + { + Emby = embySettings; + EmbyApi = emby; + Cache = cache; + Job = rec; + Repo = repo; + Jobs = jobs; + } + + private ISettingsService Emby { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + private IEmbyApi EmbyApi { get; } + private ICacheProvider Cache { get; } + private IJobRecord Job { get; } + private IRepository Repo { get; } + private ISettingsService Jobs { get; } + + private const string TableName = "EmbyEpisodes"; + + + public void CacheEpisodes(EmbySettings settings) + { + var allEpisodes = EmbyApi.GetAllEpisodes(settings.ApiKey, settings.AdministratorId, settings.FullUri); + var model = new List(); + foreach (var ep in allEpisodes.Items) + { + var epInfo = EmbyApi.GetInformation(ep.Id, EmbyMediaType.Episode, settings.ApiKey, + settings.AdministratorId, settings.FullUri); + model.Add(new EmbyEpisodes + { + EmbyId = ep.Id, + EpisodeNumber = ep.IndexNumber, + SeasonNumber = ep.ParentIndexNumber, + EpisodeTitle = ep.Name, + ParentId = ep.SeriesId, + ShowTitle = ep.SeriesName, + ProviderId = epInfo.EpisodeInformation.ProviderIds.Tmdb + }); + } + + // Delete all of the current items + Repo.DeleteAll(TableName); + + // Insert the new items + var result = Repo.BatchInsert(model, TableName, typeof(EmbyEpisodes).GetPropertyNames()); + + if (!result) + { + Log.Error("Saving the emby episodes to the DB Failed"); + } + } + + public void Start() + { + try + { + var s = Emby.GetSettings(); + if (!s.EnableEpisodeSearching) + { + return; + } + + var jobs = Job.GetJobs(); + var job = jobs.FirstOrDefault(x => x.Name.Equals(JobNames.EmbyEpisodeCacher, StringComparison.CurrentCultureIgnoreCase)); + if (job != null) + { + if (job.LastRun > DateTime.Now.AddHours(-11)) // If it's been run in the last 11 hours + { + return; + } + } + Job.SetRunning(true, JobNames.EmbyEpisodeCacher); + CacheEpisodes(s); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + Job.Record(JobNames.EmbyEpisodeCacher); + Job.SetRunning(false, JobNames.EmbyEpisodeCacher); + } + } + public void Execute(IJobExecutionContext context) + { + + try + { + var s = Emby.GetSettings(); + if (!s.EnableEpisodeSearching) + { + return; + } + + var jobs = Job.GetJobs(); + var job = jobs.FirstOrDefault(x => x.Name.Equals(JobNames.EmbyEpisodeCacher, StringComparison.CurrentCultureIgnoreCase)); + if (job != null) + { + if (job.LastRun > DateTime.Now.AddHours(-11)) // If it's been run in the last 11 hours + { + return; + } + } + Job.SetRunning(true, JobNames.EmbyEpisodeCacher); + CacheEpisodes(s); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + Job.Record(JobNames.EmbyEpisodeCacher); + Job.SetRunning(false, JobNames.EmbyEpisodeCacher); + } + } + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/EmbyUserChecker.cs b/Ombi.Services/Jobs/EmbyUserChecker.cs new file mode 100644 index 000000000..3e7c9c3f9 --- /dev/null +++ b/Ombi.Services/Jobs/EmbyUserChecker.cs @@ -0,0 +1,136 @@ +#region Copyright + +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: StoreCleanup.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 NLog; +using Ombi.Api.Interfaces; +using Ombi.Core; +using Ombi.Core.SettingModels; +using Ombi.Core.Users; +using Ombi.Helpers.Permissions; +using Ombi.Services.Interfaces; +using Ombi.Store.Models.Emby; +using Ombi.Store.Repository; +using Quartz; + +namespace Ombi.Services.Jobs +{ + public class EmbyUserChecker : IJob, IEmbyUserChecker + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + public EmbyUserChecker(IExternalUserRepository plexUsers, IEmbyApi embyApi, IJobRecord rec, ISettingsService embyS, ISettingsService prSettings, ISettingsService umSettings, + IRequestService requestService, IUserRepository localUser) + { + Repo = plexUsers; + JobRecord = rec; + EmbyApi = embyApi; + EmbySettings = embyS; + PlexRequestSettings = prSettings; + UserManagementSettings = umSettings; + RequestService = requestService; + LocalUserRepository = localUser; + } + + private IJobRecord JobRecord { get; } + private IEmbyApi EmbyApi { get; } + private IExternalUserRepository Repo { get; } + private ISettingsService EmbySettings { get; } + private ISettingsService PlexRequestSettings { get; } + private ISettingsService UserManagementSettings { get; } + private IRequestService RequestService { get; } + private IUserRepository LocalUserRepository { get; } + + public void Start() + { + JobRecord.SetRunning(true, JobNames.EmbyUserChecker); + + try + { + var settings = EmbySettings.GetSettings(); + if (string.IsNullOrEmpty(settings.ApiKey) || !settings.Enable) + { + return; + } + var embyUsers = EmbyApi.GetUsers(settings.FullUri, settings.ApiKey); + var userManagementSettings = UserManagementSettings.GetSettings(); + + var dbUsers = Repo.GetAll().ToList(); + + // Regular users + foreach (var user in embyUsers) + { + var dbUser = dbUsers.FirstOrDefault(x => x.EmbyUserId == user.Id); + if (dbUser != null) + { + // we already have a user + continue; + } + + // Looks like it's a new user! + var m = new EmbyUsers + { + EmbyUserId = user.Id, + Permissions = UserManagementHelper.GetPermissions(userManagementSettings), + Features = UserManagementHelper.GetFeatures(userManagementSettings), + UserAlias = string.Empty, + Username = user.Name, + LoginId = Guid.NewGuid().ToString() + }; + + + // If it's the admin, give them the admin permission + if (user.Policy.IsAdministrator) + { + if (!((Permissions) m.Permissions).HasFlag(Permissions.Administrator)) + { + m.Permissions += (int)Permissions.Administrator; + } + } + + Repo.Insert(m); + } + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + JobRecord.SetRunning(false, JobNames.EmbyUserChecker); + JobRecord.Record(JobNames.EmbyUserChecker); + } + } + public void Execute(IJobExecutionContext context) + { + Start(); + } + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/IEmbyUserChecker.cs b/Ombi.Services/Jobs/IEmbyUserChecker.cs new file mode 100644 index 000000000..6f1fe353c --- /dev/null +++ b/Ombi.Services/Jobs/IEmbyUserChecker.cs @@ -0,0 +1,10 @@ +using Quartz; + +namespace Ombi.Services.Jobs +{ + public interface IEmbyUserChecker + { + void Execute(IJobExecutionContext context); + void Start(); + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/Interfaces/IEmbyAvailabilityChecker.cs b/Ombi.Services/Jobs/Interfaces/IEmbyAvailabilityChecker.cs new file mode 100644 index 000000000..a954064e7 --- /dev/null +++ b/Ombi.Services/Jobs/Interfaces/IEmbyAvailabilityChecker.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Store.Models.Emby; +using Quartz; + +namespace Ombi.Services.Jobs +{ + public interface IEmbyAvailabilityChecker + { + void CheckAndUpdateAll(); + void Execute(IJobExecutionContext context); + IEnumerable GetEmbyMovies(IEnumerable content); + IEnumerable GetEmbyMusic(IEnumerable content); + IEnumerable GetEmbyTvShows(IEnumerable content); + Task> GetEpisodes(); + Task> GetEpisodes(int theTvDbId); + EmbyContent GetMovie(EmbyContent[] embyMovies, string title, string year, string providerId); + EmbyContent GetTvShow(EmbyContent[] embyShows, string title, string year, string providerId, int[] seasons = null); + bool IsEpisodeAvailable(string theTvDbId, int season, int episode); + bool IsMovieAvailable(EmbyContent[] embyMovies, string title, string year, string providerId); + bool IsTvShowAvailable(EmbyContent[] embyShows, string title, string year, string providerId, int[] seasons = null); + void Start(); + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/Interfaces/IEmbyContentCacher.cs b/Ombi.Services/Jobs/Interfaces/IEmbyContentCacher.cs new file mode 100644 index 000000000..8edf27a82 --- /dev/null +++ b/Ombi.Services/Jobs/Interfaces/IEmbyContentCacher.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Ombi.Api.Models.Emby; +using Quartz; + +namespace Ombi.Services.Jobs.Interfaces +{ + public interface IEmbyContentCacher + { + void CacheContent(); + void Execute(IJobExecutionContext context); + List GetMovies(); + List GetTvShows(); + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/Interfaces/IEmbyEpisodeCacher.cs b/Ombi.Services/Jobs/Interfaces/IEmbyEpisodeCacher.cs new file mode 100644 index 000000000..b066331ca --- /dev/null +++ b/Ombi.Services/Jobs/Interfaces/IEmbyEpisodeCacher.cs @@ -0,0 +1,12 @@ +using Ombi.Core.SettingModels; +using Quartz; + +namespace Ombi.Services.Jobs.Interfaces +{ + public interface IEmbyEpisodeCacher + { + void CacheEpisodes(EmbySettings settings); + void Execute(IJobExecutionContext context); + void Start(); + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/IFaultQueueHandler.cs b/Ombi.Services/Jobs/Interfaces/IFaultQueueHandler.cs similarity index 100% rename from Ombi.Services/Jobs/IFaultQueueHandler.cs rename to Ombi.Services/Jobs/Interfaces/IFaultQueueHandler.cs diff --git a/Ombi.Services/Jobs/IPlexEpisodeCacher.cs b/Ombi.Services/Jobs/Interfaces/IPlexEpisodeCacher.cs similarity index 100% rename from Ombi.Services/Jobs/IPlexEpisodeCacher.cs rename to Ombi.Services/Jobs/Interfaces/IPlexEpisodeCacher.cs diff --git a/Ombi.Services/Jobs/IPlexUserChecker.cs b/Ombi.Services/Jobs/Interfaces/IPlexUserChecker.cs similarity index 100% rename from Ombi.Services/Jobs/IPlexUserChecker.cs rename to Ombi.Services/Jobs/Interfaces/IPlexUserChecker.cs diff --git a/Ombi.Services/Jobs/JobNames.cs b/Ombi.Services/Jobs/JobNames.cs index 8b663a8ae..0283d7ed8 100644 --- a/Ombi.Services/Jobs/JobNames.cs +++ b/Ombi.Services/Jobs/JobNames.cs @@ -35,13 +35,17 @@ namespace Ombi.Services.Jobs public const string RadarrCacher = "Radarr Cacher"; public const string SrCacher = "SickRage Cacher"; public const string PlexChecker = "Plex Availability Cacher"; + public const string EmbyChecker = "Emby Availability Cacher"; public const string PlexCacher = "Plex Cacher"; + public const string EmbyCacher = "Emby Cacher"; public const string StoreCleanup = "Database Cleanup"; public const string RequestLimitReset = "Request Limit Reset"; public const string EpisodeCacher = "Plex Episode Cacher"; + public const string EmbyEpisodeCacher = "Emby Episode Cacher"; public const string RecentlyAddedEmail = "Recently Added Email Notification"; public const string FaultQueueHandler = "Request Fault Queue Handler"; public const string PlexUserChecker = "Plex User Checker"; + public const string EmbyUserChecker = "Emby User Checker"; } } \ No newline at end of file diff --git a/Ombi.Services/Jobs/PlexAvailabilityChecker.cs b/Ombi.Services/Jobs/PlexAvailabilityChecker.cs index e205b5b1b..241da9842 100644 --- a/Ombi.Services/Jobs/PlexAvailabilityChecker.cs +++ b/Ombi.Services/Jobs/PlexAvailabilityChecker.cs @@ -51,7 +51,7 @@ namespace Ombi.Services.Jobs public class PlexAvailabilityChecker : IJob, IAvailabilityChecker { public PlexAvailabilityChecker(ISettingsService plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache, - INotificationService notify, IJobRecord rec, IRepository users, IRepository repo, INotificationEngine e, IRepository content) + INotificationService notify, IJobRecord rec, IRepository users, IRepository repo, IPlexNotificationEngine e, IRepository content) { Plex = plexSettings; RequestService = request; @@ -152,7 +152,7 @@ namespace Ombi.Services.Jobs if (modifiedModel.Any()) { - NotificationEngine.NotifyUsers(modifiedModel, plexSettings.PlexAuthToken, NotificationType.RequestAvailable); + NotificationEngine.NotifyUsers(modifiedModel, NotificationType.RequestAvailable); RequestService.BatchUpdate(modifiedModel); } } @@ -388,7 +388,7 @@ namespace Ombi.Services.Jobs currentItem.RatingKey); // We do not want "all episodes" this as a season - var filtered = seasons.Directory.Where( x => !x.Title.Equals("All episodes", StringComparison.CurrentCultureIgnoreCase)); + var filtered = seasons.Directory.Where(x => !x.Title.Equals("All episodes", StringComparison.CurrentCultureIgnoreCase)); t1.Seasons.AddRange(filtered); } @@ -447,12 +447,15 @@ namespace Ombi.Services.Jobs private bool ValidateSettings(PlexSettings plex) { - if (plex?.Ip == null || plex?.PlexAuthToken == null) + if (plex.Enable) { - Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token."); - return false; + if (plex?.Ip == null || plex?.PlexAuthToken == null) + { + Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token."); + return false; + } } - return true; + return plex.Enable; } public void Execute(IJobExecutionContext context) diff --git a/Ombi.Services/Jobs/PlexContentCacher.cs b/Ombi.Services/Jobs/PlexContentCacher.cs index d21d26a8b..9a2d770e5 100644 --- a/Ombi.Services/Jobs/PlexContentCacher.cs +++ b/Ombi.Services/Jobs/PlexContentCacher.cs @@ -48,7 +48,7 @@ namespace Ombi.Services.Jobs public class PlexContentCacher : IJob, IPlexContentCacher { public PlexContentCacher(ISettingsService plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache, - INotificationService notify, IJobRecord rec, IRepository users, IRepository repo, INotificationEngine e, IRepository content) + INotificationService notify, IJobRecord rec, IRepository users, IRepository repo, IPlexNotificationEngine e, IRepository content) { Plex = plexSettings; RequestService = request; @@ -385,12 +385,15 @@ namespace Ombi.Services.Jobs private bool ValidateSettings(PlexSettings plex) { - if (plex?.Ip == null || plex?.PlexAuthToken == null) + if (plex.Enable) { - Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token."); - return false; + if (plex?.Ip == null || plex?.PlexAuthToken == null) + { + Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token."); + return false; + } } - return true; + return plex.Enable; } public void Execute(IJobExecutionContext context) diff --git a/Ombi.Services/Jobs/PlexEpisodeCacher.cs b/Ombi.Services/Jobs/PlexEpisodeCacher.cs index b7af87022..e6d1fc9c9 100644 --- a/Ombi.Services/Jobs/PlexEpisodeCacher.cs +++ b/Ombi.Services/Jobs/PlexEpisodeCacher.cs @@ -38,8 +38,10 @@ using Ombi.Core.SettingModels; using Ombi.Helpers; using Ombi.Services.Interfaces; using Ombi.Store.Models; +using Ombi.Store.Models.Plex; using Ombi.Store.Repository; using Quartz; +using PlexMediaType = Ombi.Api.Models.Plex.PlexMediaType; namespace Ombi.Services.Jobs { diff --git a/Ombi.Services/Jobs/PlexUserChecker.cs b/Ombi.Services/Jobs/PlexUserChecker.cs index 3303d1dcf..7a56ddeee 100644 --- a/Ombi.Services/Jobs/PlexUserChecker.cs +++ b/Ombi.Services/Jobs/PlexUserChecker.cs @@ -37,6 +37,7 @@ using Ombi.Core.Users; using Ombi.Helpers.Permissions; using Ombi.Services.Interfaces; using Ombi.Store.Models; +using Ombi.Store.Models.Plex; using Ombi.Store.Repository; using Quartz; @@ -46,7 +47,7 @@ namespace Ombi.Services.Jobs { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - public PlexUserChecker(IPlexUserRepository plexUsers, IPlexApi plexAPi, IJobRecord rec, ISettingsService plexSettings, ISettingsService prSettings, ISettingsService umSettings, + public PlexUserChecker(IExternalUserRepository plexUsers, IPlexApi plexAPi, IJobRecord rec, ISettingsService plexSettings, ISettingsService prSettings, ISettingsService umSettings, IRequestService requestService, IUserRepository localUser) { Repo = plexUsers; @@ -61,7 +62,7 @@ namespace Ombi.Services.Jobs private IJobRecord JobRecord { get; } private IPlexApi PlexApi { get; } - private IPlexUserRepository Repo { get; } + private IExternalUserRepository Repo { get; } private ISettingsService PlexSettings { get; } private ISettingsService PlexRequestSettings { get; } private ISettingsService UserManagementSettings { get; } @@ -75,7 +76,7 @@ namespace Ombi.Services.Jobs try { var settings = PlexSettings.GetSettings(); - if (string.IsNullOrEmpty(settings.PlexAuthToken)) + if (string.IsNullOrEmpty(settings.PlexAuthToken) || !settings.Enable) { return; } diff --git a/Ombi.Services/Jobs/RecentlyAdded.cs b/Ombi.Services/Jobs/RecentlyAdded.cs index 8ac7b6743..563dd35a5 100644 --- a/Ombi.Services/Jobs/RecentlyAdded.cs +++ b/Ombi.Services/Jobs/RecentlyAdded.cs @@ -200,16 +200,17 @@ namespace Ombi.Services.Jobs html = template.LoadTemplate(sb.ToString()); Log.Debug("Loaded the template"); } - - Send(newletterSettings, html, plexSettings, testEmail); + string escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray()); + Log.Debug(escapedHtml); + Send(newletterSettings, escapedHtml, plexSettings, 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("

New Movies:



"); sb.Append( ""); foreach (var movie in orderedMovies) @@ -259,13 +260,13 @@ namespace Ombi.Services.Jobs } } - sb.Append("


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

New Movies:



"); sb.Append( ""); foreach (var movie in orderedMovies) @@ -315,14 +316,14 @@ namespace Ombi.Services.Jobs } } - sb.Append("


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

New Episodes:



"); sb.Append( ""); foreach (var t in orderedTv) @@ -375,14 +376,14 @@ namespace Ombi.Services.Jobs EndLoopHtml(sb); } } - sb.Append("


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

New Episodes:



"); sb.Append( ""); foreach (var t in orderedTv) @@ -435,7 +436,7 @@ namespace Ombi.Services.Jobs EndLoopHtml(sb); } } - sb.Append("


"); + sb.Append("

"); } private void Send(NewletterSettings newletterSettings, string html, PlexSettings plexSettings, bool testEmail = false) @@ -516,10 +517,12 @@ namespace Ombi.Services.Jobs 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("
"); - sb.Append("
"); - sb.Append("
"); sb.Append(""); } diff --git a/Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html b/Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html index 2229df38c..f5b208138 100644 --- a/Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html +++ b/Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html @@ -149,8 +149,8 @@ -
-
+
+

Here is a list of Movies and TV Shows that have recently been added to Plex!

diff --git a/Ombi.Services/Notification/EmbyNotificationEngine.cs b/Ombi.Services/Notification/EmbyNotificationEngine.cs new file mode 100644 index 000000000..ed8f587a6 --- /dev/null +++ b/Ombi.Services/Notification/EmbyNotificationEngine.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NLog; +using Ombi.Api.Interfaces; +using Ombi.Core; +using Ombi.Core.Models; +using Ombi.Core.SettingModels; +using Ombi.Core.Users; +using Ombi.Helpers.Permissions; +using Ombi.Services.Interfaces; +using Ombi.Store; +using Ombi.Store.Models; +using Ombi.Store.Models.Emby; +using Ombi.Store.Repository; + +namespace Ombi.Services.Notification +{ + public class EmbyNotificationEngine : IEmbyNotificationEngine + { + public EmbyNotificationEngine(IEmbyApi p, IRepository repo, ISettingsService embySettings, INotificationService service, IUserHelper userHelper, IExternalUserRepository embyUsers) + { + EmbyApi = p; + UserNotifyRepo = repo; + Notification = service; + UserHelper = userHelper; + EmbySettings = embySettings; + EmbyUserRepo = embyUsers; + } + + private IEmbyApi EmbyApi { get; } + private IRepository UserNotifyRepo { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + private INotificationService Notification { get; } + private IUserHelper UserHelper { get; } + private ISettingsService EmbySettings { get; } + private IExternalUserRepository EmbyUserRepo { get; } + + public async Task NotifyUsers(IEnumerable modelChanged, NotificationType type) + { + try + { + var embySettings = await EmbySettings.GetSettingsAsync(); + var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey); + var userAccount = embyUsers.FirstOrDefault(x => x.Policy.IsAdministrator); + + var adminUsername = userAccount?.Name ?? string.Empty; + + var users = UserHelper.GetUsersWithFeature(Features.RequestAddedNotification).ToList(); + Log.Debug("Notifying Users Count {0}", users.Count); + foreach (var model in modelChanged) + { + var selectedUsers = new List(); + + foreach (var u in users) + { + var requestUser = model.RequestedUsers.FirstOrDefault( + x => x.Equals(u.Username, StringComparison.CurrentCultureIgnoreCase) || x.Equals(u.UserAlias, StringComparison.CurrentCultureIgnoreCase)); + if (string.IsNullOrEmpty(requestUser)) + { + continue; + } + + // Make sure we do not already have the user + if (!selectedUsers.Contains(requestUser)) + { + selectedUsers.Add(requestUser); + } + } + + foreach (var user in selectedUsers) + { + var localUser = + users.FirstOrDefault(x => + x.Username.Equals(user, StringComparison.CurrentCultureIgnoreCase) || + x.UserAlias.Equals(user, StringComparison.CurrentCultureIgnoreCase)); + Log.Info("Notifying user {0}", user); + if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase)) + { + Log.Info("This user is the Plex server owner"); + await PublishUserNotification(userAccount?.Name, localUser?.EmailAddress, model.Title, model.PosterPath, type, model.Type); + return; + } + + + + // So if the request was from an alias, then we need to use the local user (since that contains the alias). + // If we do not have a local user, then we should be using the Emby user if that user exists. + // This will execute most of the time since Emby and Local users will most always be in the database. + if (localUser != null) + { + if (string.IsNullOrEmpty(localUser?.EmailAddress)) + { + Log.Info("There is no email address for this Local user ({0}), cannot send notification", localUser.Username); + continue; + } + + Log.Info("Sending notification to: {0} at: {1}, for : {2}", localUser, localUser.EmailAddress, model.Title); + await PublishUserNotification(localUser.Username, localUser.EmailAddress, model.Title, model.PosterPath, type, model.Type); + + } + else + { + var embyUser = EmbyUserRepo.GetUserByUsername(user); + var email = embyUsers.FirstOrDefault(x => x.Name.Equals(user, StringComparison.CurrentCultureIgnoreCase)); + if (string.IsNullOrEmpty(embyUser?.EmailAddress)) // TODO this needs to be the email + { + Log.Info("There is no email address for this Emby user ({0}), cannot send notification", email?.Name); + // We do not have a plex user that requested this! + continue; + } + + Log.Info("Sending notification to: {0} at: {1}, for : {2}", embyUser?.Username, embyUser?.EmailAddress, model.Title); + await PublishUserNotification(email?.Name, embyUser?.EmailAddress, model.Title, model.PosterPath, type, model.Type); + } + } + } + } + catch (Exception e) + { + Log.Error(e); + } + } + + public async Task NotifyUsers(RequestedModel model, NotificationType type) + { + try + { + var embySettings = await EmbySettings.GetSettingsAsync(); + var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey); + var userAccount = embyUsers.FirstOrDefault(x => x.Policy.IsAdministrator); + + var adminUsername = userAccount.Name ?? string.Empty; + + var users = UserHelper.GetUsersWithFeature(Features.RequestAddedNotification).ToList(); + Log.Debug("Notifying Users Count {0}", users.Count); + + // Get the usernames or alias depending if they have an alias + var userNamesWithFeature = users.Select(x => x.UsernameOrAlias).ToList(); + Log.Debug("Users with the feature count {0}", userNamesWithFeature.Count); + Log.Debug("Usernames: "); + foreach (var u in userNamesWithFeature) + { + Log.Debug(u); + } + + Log.Debug("Users in the requested model count: {0}", model.AllUsers.Count); + Log.Debug("usernames from model: "); + foreach (var modelAllUser in model.AllUsers) + { + Log.Debug(modelAllUser); + } + + if (model.AllUsers == null || !model.AllUsers.Any()) + { + Log.Debug("There are no users in the model.AllUsers, no users to notify"); + return; + } + var usersToNotify = userNamesWithFeature.Intersect(model.AllUsers, StringComparer.CurrentCultureIgnoreCase).ToList(); + + if (!usersToNotify.Any()) + { + Log.Debug("Could not find any users after the .Intersect()"); + } + + Log.Debug("Users being notified for this request count {0}", users.Count); + foreach (var user in usersToNotify) + { + var embyUser = EmbyUserRepo.GetUserByUsername(user); + Log.Info("Notifying user {0}", user); + if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase)) + { + Log.Info("This user is the Emby server owner"); + await PublishUserNotification(userAccount.Name, embyUser.EmailAddress, model.Title, model.PosterPath, type, model.Type); + return; + } + + var email = embyUsers.FirstOrDefault(x => x.Name.Equals(user, StringComparison.CurrentCultureIgnoreCase)); + if (email == null) + { + Log.Info("There is no email address for this Emby user, cannot send notification"); + // We do not have a emby user that requested this! + continue; + } + + Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Name, embyUser.EmailAddress, model.Title); + await PublishUserNotification(email.Name, embyUser.EmailAddress, model.Title, model.PosterPath, type, model.Type); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + + private async Task PublishUserNotification(string username, string email, string title, string img, NotificationType type, RequestType requestType) + { + var notificationModel = new NotificationModel + { + User = username, + UserEmail = email, + NotificationType = type, + Title = title, + ImgSrc = requestType == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{img}" : img + }; + + // Send the notification to the user. + await Notification.Publish(notificationModel); + } + } +} \ No newline at end of file diff --git a/Ombi.Services/Notification/NotificationEngine.cs b/Ombi.Services/Notification/PlexNotificationEngine.cs similarity index 91% rename from Ombi.Services/Notification/NotificationEngine.cs rename to Ombi.Services/Notification/PlexNotificationEngine.cs index 2f51d7109..9fbd7dc59 100644 --- a/Ombi.Services/Notification/NotificationEngine.cs +++ b/Ombi.Services/Notification/PlexNotificationEngine.cs @@ -1,7 +1,7 @@ #region Copyright // /************************************************************************ // Copyright (c) 2016 Jamie Rees -// File: NotificationEngine.cs +// File: PlexNotificationEngine.cs // Created By: Jamie Rees // // Permission is hereby granted, free of charge, to any person obtaining @@ -31,7 +31,9 @@ using System.Linq; using System.Threading.Tasks; using NLog; using Ombi.Api.Interfaces; +using Ombi.Core; using Ombi.Core.Models; +using Ombi.Core.SettingModels; using Ombi.Core.Users; using Ombi.Helpers.Permissions; using Ombi.Services.Interfaces; @@ -41,14 +43,15 @@ using Ombi.Store.Repository; namespace Ombi.Services.Notification { - public class NotificationEngine : INotificationEngine + public class PlexNotificationEngine : IPlexNotificationEngine { - public NotificationEngine(IPlexApi p, IRepository repo, INotificationService service, IUserHelper userHelper) + public PlexNotificationEngine(IPlexApi p, IRepository repo, INotificationService service, IUserHelper userHelper, ISettingsService ps) { PlexApi = p; UserNotifyRepo = repo; Notification = service; UserHelper = userHelper; + PlexSettings = ps; } private IPlexApi PlexApi { get; } @@ -56,13 +59,15 @@ namespace Ombi.Services.Notification private static Logger Log = LogManager.GetCurrentClassLogger(); private INotificationService Notification { get; } private IUserHelper UserHelper { get; } + private ISettingsService PlexSettings { get; } - public async Task NotifyUsers(IEnumerable modelChanged, string apiKey, NotificationType type) + public async Task NotifyUsers(IEnumerable modelChanged, NotificationType type) { try { - var plexUser = PlexApi.GetUsers(apiKey); - var userAccount = PlexApi.GetAccount(apiKey); + var settings = await PlexSettings.GetSettingsAsync(); + var plexUser = PlexApi.GetUsers(settings.PlexAuthToken); + var userAccount = PlexApi.GetAccount(settings.PlexAuthToken); var adminUsername = userAccount.Username ?? string.Empty; @@ -161,12 +166,13 @@ namespace Ombi.Services.Notification } } - public async Task NotifyUsers(RequestedModel model, string apiKey, NotificationType type) + public async Task NotifyUsers(RequestedModel model, NotificationType type) { try { - var plexUser = PlexApi.GetUsers(apiKey); - var userAccount = PlexApi.GetAccount(apiKey); + var settings = await PlexSettings.GetSettingsAsync(); + var plexUser = PlexApi.GetUsers(settings.PlexAuthToken); + var userAccount = PlexApi.GetAccount(settings.PlexAuthToken); var adminUsername = userAccount.Username ?? string.Empty; diff --git a/Ombi.Services/Ombi.Services.csproj b/Ombi.Services/Ombi.Services.csproj index 6705cf939..1735094dd 100644 --- a/Ombi.Services/Ombi.Services.csproj +++ b/Ombi.Services/Ombi.Services.csproj @@ -86,6 +86,8 @@ + + @@ -93,9 +95,17 @@ - - - + + + + + + + + + + + @@ -129,7 +139,8 @@ - + + diff --git a/Ombi.Store/Models/Emby/EmbyContent.cs b/Ombi.Store/Models/Emby/EmbyContent.cs new file mode 100644 index 000000000..799487755 --- /dev/null +++ b/Ombi.Store/Models/Emby/EmbyContent.cs @@ -0,0 +1,43 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: Emby.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; +using Ombi.Store.Models.Plex; + +namespace Ombi.Store.Models.Emby +{ + [Table(nameof(EmbyContent))] + public class EmbyContent : Entity + { + public string Title { get; set; } + public string EmbyId { get; set; } + public DateTime PremierDate { get; set; } + public string ProviderId { get; set; } + public EmbyMediaType Type { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Store/Models/Emby/EmbyEpisodes.cs b/Ombi.Store/Models/Emby/EmbyEpisodes.cs new file mode 100644 index 000000000..a1b900455 --- /dev/null +++ b/Ombi.Store/Models/Emby/EmbyEpisodes.cs @@ -0,0 +1,43 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyEpisodes.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 Dapper.Contrib.Extensions; + +namespace Ombi.Store.Models.Emby +{ + [Table(nameof(EmbyEpisodes))] + public class EmbyEpisodes : Entity + { + public string EpisodeTitle { get; set; } + public string ShowTitle { get; set; } + public string EmbyId { get; set; } + public int EpisodeNumber { get; set; } + public int SeasonNumber { get; set; } + public string ParentId { get; set; } + public string ProviderId { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Store/Models/Emby/EmbyMediaType.cs b/Ombi.Store/Models/Emby/EmbyMediaType.cs new file mode 100644 index 000000000..eaf2fac85 --- /dev/null +++ b/Ombi.Store/Models/Emby/EmbyMediaType.cs @@ -0,0 +1,35 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexMediaType .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.Store.Models.Plex +{ + public enum EmbyMediaType + { + Movie = 0, + Series = 1, + Music = 2 + } +} \ No newline at end of file diff --git a/Ombi.Store/Models/Emby/EmbyUsers.cs b/Ombi.Store/Models/Emby/EmbyUsers.cs new file mode 100644 index 000000000..0f90dd025 --- /dev/null +++ b/Ombi.Store/Models/Emby/EmbyUsers.cs @@ -0,0 +1,43 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexUsers.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 Dapper.Contrib.Extensions; + +namespace Ombi.Store.Models.Emby +{ + [Table(nameof(EmbyUsers))] + public class EmbyUsers : Entity + { + public string EmbyUserId { get; set; } + public string UserAlias { get; set; } + public int Permissions { get; set; } + public int Features { get; set; } + public string Username { get; set; } + public string EmailAddress { get; set; } + public string LoginId { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Store/Models/PlexEpisodes.cs b/Ombi.Store/Models/Plex/PlexEpisodes.cs similarity index 98% rename from Ombi.Store/Models/PlexEpisodes.cs rename to Ombi.Store/Models/Plex/PlexEpisodes.cs index 922fab03c..8fe994103 100644 --- a/Ombi.Store/Models/PlexEpisodes.cs +++ b/Ombi.Store/Models/Plex/PlexEpisodes.cs @@ -27,7 +27,7 @@ using Dapper.Contrib.Extensions; -namespace Ombi.Store.Models +namespace Ombi.Store.Models.Plex { [Table("PlexEpisodes")] public class PlexEpisodes : Entity diff --git a/Ombi.Store/Models/PlexUsers.cs b/Ombi.Store/Models/Plex/PlexUsers.cs similarity index 97% rename from Ombi.Store/Models/PlexUsers.cs rename to Ombi.Store/Models/Plex/PlexUsers.cs index 0a3b735d1..f75e6ff31 100644 --- a/Ombi.Store/Models/PlexUsers.cs +++ b/Ombi.Store/Models/Plex/PlexUsers.cs @@ -26,9 +26,8 @@ #endregion using Dapper.Contrib.Extensions; -using Newtonsoft.Json; -namespace Ombi.Store.Models +namespace Ombi.Store.Models.Plex { [Table(nameof(PlexUsers))] public class PlexUsers : Entity diff --git a/Ombi.Store/Ombi.Store.csproj b/Ombi.Store/Ombi.Store.csproj index ea9ca2469..6bfaa021b 100644 --- a/Ombi.Store/Ombi.Store.csproj +++ b/Ombi.Store/Ombi.Store.csproj @@ -65,11 +65,15 @@ + + - - + + + + @@ -77,6 +81,8 @@ + + @@ -88,7 +94,6 @@ - diff --git a/Ombi.Store/Repository/PlexUserRepository.cs b/Ombi.Store/Repository/BaseExternalUserRepository.cs similarity index 60% rename from Ombi.Store/Repository/PlexUserRepository.cs rename to Ombi.Store/Repository/BaseExternalUserRepository.cs index 69bb31816..7206a5bc5 100644 --- a/Ombi.Store/Repository/PlexUserRepository.cs +++ b/Ombi.Store/Repository/BaseExternalUserRepository.cs @@ -26,18 +26,18 @@ #endregion using System; -using System.Collections.Generic; using System.Data; using System.Threading.Tasks; using Dapper; using Ombi.Helpers; -using Ombi.Store.Models; +using Ombi.Store.Models.Emby; +using Ombi.Store.Models.Plex; namespace Ombi.Store.Repository { - public class PlexUserRepository : BaseGenericRepository, IPlexUserRepository + public class BaseExternalUserRepository : BaseGenericRepository, IExternalUserRepository where T : Entity { - public PlexUserRepository(ISqliteConfiguration config, ICacheProvider cache) : base(config,cache) + public BaseExternalUserRepository(ISqliteConfiguration config, ICacheProvider cache) : base(config,cache) { DbConfig = config; } @@ -45,53 +45,69 @@ namespace Ombi.Store.Repository private ISqliteConfiguration DbConfig { get; } private IDbConnection Db => DbConfig.DbConnection(); - public PlexUsers GetUser(string userGuid) + private string TableName { - var sql = @"SELECT * FROM PlexUsers + get + { + if (typeof(T) == typeof(PlexUsers)) + { + return "PlexUsers"; + } + if (typeof(T) == typeof(EmbyUsers)) + { + return "EmbyUsers"; + } + return string.Empty; + } + } + + public T GetUser(string userGuid) + { + var sql = $@"SELECT * FROM {TableName} WHERE PlexUserId = @UserGuid COLLATE NOCASE"; - return Db.QueryFirstOrDefault(sql, new {UserGuid = userGuid}); + return Db.QueryFirstOrDefault(sql, new {UserGuid = userGuid}); } - public PlexUsers GetUserByUsername(string username) + public T GetUserByUsername(string username) { - var sql = @"SELECT * FROM PlexUsers + var sql = $@"SELECT * FROM {TableName} WHERE Username = @UserName COLLATE NOCASE"; - return Db.QueryFirstOrDefault(sql, new {UserName = username}); + return Db.QueryFirstOrDefault(sql, new {UserName = username}); } - public async Task GetUserAsync(string userguid) + public async Task GetUserAsync(string userguid) { - var sql = @"SELECT * FROM PlexUsers + var sql = $@"SELECT * FROM {TableName} WHERE PlexUserId = @UserGuid COLLATE NOCASE"; - return await Db.QueryFirstOrDefaultAsync(sql, new {UserGuid = userguid}); + return await Db.QueryFirstOrDefaultAsync(sql, new {UserGuid = userguid}); } #region abstract implementation #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member [Obsolete] - public override PlexUsers Get(string id) + public override T Get(string id) { throw new System.NotImplementedException(); } [Obsolete] - public override Task GetAsync(int id) + public override Task GetAsync(int id) { throw new System.NotImplementedException(); } [Obsolete] - public override PlexUsers Get(int id) + public override T Get(int id) { throw new System.NotImplementedException(); } [Obsolete] - public override Task GetAsync(string id) + public override Task GetAsync(string id) { throw new System.NotImplementedException(); } @@ -99,22 +115,5 @@ namespace Ombi.Store.Repository #pragma warning restore CS0809 // Obsolete member overrides non-obsolete member #endregion } - - - public interface IPlexUserRepository - { - PlexUsers GetUser(string userGuid); - PlexUsers GetUserByUsername(string username); - Task GetUserAsync(string userguid); - IEnumerable Custom(Func> func); - long Insert(PlexUsers entity); - void Delete(PlexUsers entity); - IEnumerable GetAll(); - bool UpdateAll(IEnumerable entity); - bool Update(PlexUsers entity); - Task> GetAllAsync(); - Task UpdateAsync(PlexUsers users); - Task InsertAsync(PlexUsers users); - } } diff --git a/Ombi.Store/Repository/IExternalUserRepository.cs b/Ombi.Store/Repository/IExternalUserRepository.cs new file mode 100644 index 000000000..7d0587edc --- /dev/null +++ b/Ombi.Store/Repository/IExternalUserRepository.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading.Tasks; + +namespace Ombi.Store.Repository +{ + public interface IExternalUserRepository where T : Entity + { + T Get(string id); + T Get(int id); + Task GetAsync(string id); + Task GetAsync(int id); + T GetUser(string userGuid); + Task GetUserAsync(string userguid); + T GetUserByUsername(string username); + + IEnumerable Custom(Func> func); + long Insert(T entity); + void Delete(T entity); + IEnumerable GetAll(); + bool UpdateAll(IEnumerable entity); + bool Update(T entity); + Task> GetAllAsync(); + Task UpdateAsync(T users); + Task InsertAsync(T users); + } +} \ No newline at end of file diff --git a/Ombi.Store/SqlTables.sql b/Ombi.Store/SqlTables.sql index 90116a206..f548381a8 100644 --- a/Ombi.Store/SqlTables.sql +++ b/Ombi.Store/SqlTables.sql @@ -124,6 +124,20 @@ CREATE TABLE IF NOT EXISTS PlexUsers ); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id); + +CREATE TABLE IF NOT EXISTS EmbyUsers +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + EmbyUserId varchar(100) NOT NULL, + UserAlias varchar(100) NOT NULL, + Permissions INTEGER, + Features INTEGER, + Username VARCHAR(100), + EmailAddress VARCHAR(100), + LoginId VARCHAR(100) +); +CREATE UNIQUE INDEX IF NOT EXISTS EmbyUsers_Id ON EmbyUsers (Id); + BEGIN; CREATE TABLE IF NOT EXISTS PlexEpisodes ( @@ -164,4 +178,29 @@ CREATE TABLE IF NOT EXISTS PlexContent ); CREATE UNIQUE INDEX IF NOT EXISTS PlexContent_Id ON PlexContent (Id); +CREATE TABLE IF NOT EXISTS EmbyEpisodes +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + EpisodeTitle VARCHAR(100) NOT NULL, + ShowTitle VARCHAR(100) NOT NULL, + EmbyId VARCHAR(100) NOT NULL, + SeasonNumber INTEGER NOT NULL, + EpisodeNumber INTEGER NOT NULL, + ParentId VARCHAR(100) NOT NULL, + ProviderId VARCHAR(100) NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS EmbyEpisodes_Id ON EmbyEpisodes (Id); + +CREATE TABLE IF NOT EXISTS EmbyContent +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Title VARCHAR(100) NOT NULL, + PremierDate VARCHAR(100) NOT NULL, + EmbyId VARCHAR(100) NOT NULL, + ProviderId VARCHAR(100) NOT NULL, + Type INTEGER NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS EmbyEpisodes_Id ON EmbyEpisodes (Id); + + COMMIT; \ No newline at end of file diff --git a/Ombi.UI/Authentication/CustomAuthenticationConfiguration.cs b/Ombi.UI/Authentication/CustomAuthenticationConfiguration.cs index d730a798e..bdf00e396 100644 --- a/Ombi.UI/Authentication/CustomAuthenticationConfiguration.cs +++ b/Ombi.UI/Authentication/CustomAuthenticationConfiguration.cs @@ -26,6 +26,8 @@ #endregion using Nancy.Cryptography; +using Ombi.Store.Models.Emby; +using Ombi.Store.Models.Plex; using Ombi.Store.Repository; namespace Ombi.UI.Authentication @@ -47,7 +49,8 @@ namespace Ombi.UI.Authentication /// Gets or sets the username/identifier mapper public IUserRepository LocalUserRepository { get; set; } - public IPlexUserRepository PlexUserRepository { get; set; } + public IExternalUserRepository PlexUserRepository { get; set; } + public IExternalUserRepository EmbyUserRepository { get; set; } /// Gets or sets RequiresSSL property /// The flag that indicates whether SSL is required diff --git a/Ombi.UI/Bootstrapper.cs b/Ombi.UI/Bootstrapper.cs index aa14ed0ab..acb9d871e 100644 --- a/Ombi.UI/Bootstrapper.cs +++ b/Ombi.UI/Bootstrapper.cs @@ -42,6 +42,8 @@ using Ombi.Core; using Ombi.Core.SettingModels; using Ombi.Helpers; using Ombi.Store; +using Ombi.Store.Models.Emby; +using Ombi.Store.Models.Plex; using Ombi.Store.Repository; using Ombi.UI.Authentication; using Ombi.UI.Helpers; @@ -88,7 +90,8 @@ namespace Ombi.UI var config = new CustomAuthenticationConfiguration { RedirectUrl = redirect, - PlexUserRepository = container.Get(), + PlexUserRepository = container.Get>(), + EmbyUserRepository = container.Get>(), LocalUserRepository = container.Get() }; diff --git a/Ombi.UI/Content/app/userManagement/Directives/sidebar.html b/Ombi.UI/Content/app/userManagement/Directives/sidebar.html index 799c133fe..571ed2f59 100644 --- a/Ombi.UI/Content/app/userManagement/Directives/sidebar.html +++ b/Ombi.UI/Content/app/userManagement/Directives/sidebar.html @@ -12,7 +12,10 @@ Email Address:
- User Type: + User Type: + Local User + Emby User + Plex User


@@ -49,7 +52,7 @@ - + diff --git a/Ombi.UI/Content/app/userManagement/Directives/table.html b/Ombi.UI/Content/app/userManagement/Directives/table.html index 587acc988..8c7b381e5 100644 --- a/Ombi.UI/Content/app/userManagement/Directives/table.html +++ b/Ombi.UI/Content/app/userManagement/Directives/table.html @@ -75,7 +75,9 @@ {{u.permissionsFormattedString}} - {{u.type === 1 ? 'Local User' : 'Plex User'}} + Local User + Plex User + Emby User diff --git a/Ombi.UI/Content/app/userManagement/userManagementController.js b/Ombi.UI/Content/app/userManagement/userManagementController.js index 56f68005e..19eb70f42 100644 --- a/Ombi.UI/Content/app/userManagement/userManagementController.js +++ b/Ombi.UI/Content/app/userManagement/userManagementController.js @@ -20,7 +20,7 @@ $scope.searchTerm = ""; $scope.hideColumns = false; - + var ReadOnlyPermission = "Read Only User"; var open = false; diff --git a/Ombi.UI/Content/base.css b/Ombi.UI/Content/base.css index 987834924..2b67cfa3e 100644 --- a/Ombi.UI/Content/base.css +++ b/Ombi.UI/Content/base.css @@ -517,3 +517,11 @@ label { background-color: #3e3e3e; border: 1px solid transparent; } +.wizard-heading { + text-align: center; } + +.wizard-img { + width: 300px; + display: block !important; + margin: 0 auto !important; } + diff --git a/Ombi.UI/Content/base.min.css b/Ombi.UI/Content/base.min.css index 4ac0fc57e..164bdc9a5 100644 --- a/Ombi.UI/Content/base.min.css +++ b/Ombi.UI/Content/base.min.css @@ -1 +1 @@ -@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}.landing-block .media{max-width:450px;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#fff;}hr{border:1px dashed #777;}.btn{border-radius:.25rem !important;}.btn-group-separated .btn,.btn-group-separated .btn+.btn{margin-left:3px;}.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;}.small-label{display:inline-block !important;margin-bottom:.5rem !important;font-size:11px !important;}.small-checkbox{min-height:0 !important;}.round-checkbox{border-radius:8px;}.nav-tabs>li{font-size:13px;line-height:21px;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.nav-tabs>li>a>.fa{padding:3px 5px 3px 3px;}.nav-tabs>li.nav-tab-right{float:right;}.nav-tabs>li.nav-tab-right a{margin-right:0;margin-left:2px;}.nav-tabs>li.nav-tab-icononly .fa{padding:3px;}.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:10px;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:#df691a;text-align:center;font-size:15px;padding:3px 0;}#cacherRunning{background-color:#4e5d6c;text-align:center;font-size:15px;padding:3px 0;}.checkbox label{display:inline-block;cursor:pointer;position:relative;padding-left:25px;margin-right:15px;font-size:13px;margin-bottom:10px;}.checkbox label:before{content:"";display:inline-block;width:18px;height:18px;margin-right:10px;position:absolute;left:0;bottom:1px;border:2px solid #eee;border-radius:3px;}.checkbox input[type=checkbox]{display:none;}.checkbox input[type=checkbox]:checked+label:before{content:"✓";font-size:13px;color:#fafafa;text-align:center;line-height:13px;}.small-checkbox label{display:inline-block;cursor:pointer;position:relative;padding-left:25px;margin-right:15px;font-size:13px;margin-bottom:10px;}.small-checkbox label:before{content:"";display:inline-block;width:18px;height:18px;margin-right:10px;position:absolute;left:0;bottom:1px;border:2px solid #eee;border-radius:8px;min-height:0 !important;}.small-checkbox input[type=checkbox]{display:none;}.small-checkbox input[type=checkbox]:checked+label:before{content:"✓";font-size:13px;color:#fafafa;text-align:center;line-height:13px;}.small-checkbox label{min-height:0 !important;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer;}.input-group-sm{padding-top:2px;padding-bottom:2px;}.tab-pane .form-horizontal .form-group{margin-right:15px;margin-left:15px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#4e5d6c;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #4e5d6c !important;}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{color:#fff !important;}.landing-header{display:block;margin:60px auto;}.landing-block{background:#2f2f2f !important;padding:5px;}.landing-block .media{margin:30px auto;max-width:450px;}.landing-block .media .media-left{display:inline-block;float:left;width:70px;}.landing-block .media .media-left i.fa{font-size:3em;}.landing-title{font-weight:bold;}.checkbox-custom{margin-top:0 !important;margin-bottom:0 !important;}.tooltip_templates{display:none;}.shadow{-moz-box-shadow:3px 3px 5px 6px #191919;-webkit-box-shadow:3px 3px 5px 6px #191919;box-shadow:3px 3px 5px 6px #191919;}.img-circle{border-radius:50%;}#wrapper{padding-left:0;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled{padding-right:250px;}#sidebar-wrapper{z-index:1000;position:fixed;right:250px;width:0;height:100%;margin-right:-250px;overflow-y:auto;background:#4e5d6c;padding-left:0;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled #sidebar-wrapper{width:500px;}#page-content-wrapper{width:100%;position:absolute;padding:15px;}#wrapper.toggled #page-content-wrapper{position:absolute;margin-left:-250px;}.sidebar-nav{position:absolute;top:0;width:500px;margin:0;padding-left:0;list-style:none;}.sidebar-nav li{text-indent:20px;line-height:40px;}.sidebar-nav li a{display:block;text-decoration:none;color:#999;}.sidebar-nav li a:hover{text-decoration:none;color:#fff;background:rgba(255,255,255,.2);}.sidebar-nav li a:active,.sidebar-nav li a:focus{text-decoration:none;}.sidebar-nav>.sidebar-brand{height:65px;font-size:18px;line-height:60px;}.sidebar-nav>.sidebar-brand a{color:#999;}.sidebar-nav>.sidebar-brand a:hover{color:#fff;background:none;}@media(min-width:768px){#wrapper{padding-right:250px;}#wrapper.toggled{padding-right:0;}#sidebar-wrapper{width:500px;}#wrapper.toggled #sidebar-wrapper{width:0;}#page-content-wrapper{padding:20px;position:relative;}#wrapper.toggled #page-content-wrapper{position:relative;margin-right:0;}}#lightbox{background-color:#808080;filter:alpha(opacity=50);opacity:.5;-moz-opacity:.5;top:0;left:0;z-index:20;height:100%;width:100%;background-repeat:no-repeat;background-position:center;position:absolute;}.list-group-item-dropdown{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#3e3e3e;border:1px solid transparent;} \ No newline at end of file +@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}.landing-block .media{max-width:450px;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#fff;}hr{border:1px dashed #777;}.btn{border-radius:.25rem !important;}.btn-group-separated .btn,.btn-group-separated .btn+.btn{margin-left:3px;}.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;}.small-label{display:inline-block !important;margin-bottom:.5rem !important;font-size:11px !important;}.small-checkbox{min-height:0 !important;}.round-checkbox{border-radius:8px;}.nav-tabs>li{font-size:13px;line-height:21px;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.nav-tabs>li>a>.fa{padding:3px 5px 3px 3px;}.nav-tabs>li.nav-tab-right{float:right;}.nav-tabs>li.nav-tab-right a{margin-right:0;margin-left:2px;}.nav-tabs>li.nav-tab-icononly .fa{padding:3px;}.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:10px;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:#df691a;text-align:center;font-size:15px;padding:3px 0;}#cacherRunning{background-color:#4e5d6c;text-align:center;font-size:15px;padding:3px 0;}.checkbox label{display:inline-block;cursor:pointer;position:relative;padding-left:25px;margin-right:15px;font-size:13px;margin-bottom:10px;}.checkbox label:before{content:"";display:inline-block;width:18px;height:18px;margin-right:10px;position:absolute;left:0;bottom:1px;border:2px solid #eee;border-radius:3px;}.checkbox input[type=checkbox]{display:none;}.checkbox input[type=checkbox]:checked+label:before{content:"✓";font-size:13px;color:#fafafa;text-align:center;line-height:13px;}.small-checkbox label{display:inline-block;cursor:pointer;position:relative;padding-left:25px;margin-right:15px;font-size:13px;margin-bottom:10px;}.small-checkbox label:before{content:"";display:inline-block;width:18px;height:18px;margin-right:10px;position:absolute;left:0;bottom:1px;border:2px solid #eee;border-radius:8px;min-height:0 !important;}.small-checkbox input[type=checkbox]{display:none;}.small-checkbox input[type=checkbox]:checked+label:before{content:"✓";font-size:13px;color:#fafafa;text-align:center;line-height:13px;}.small-checkbox label{min-height:0 !important;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer;}.input-group-sm{padding-top:2px;padding-bottom:2px;}.tab-pane .form-horizontal .form-group{margin-right:15px;margin-left:15px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#4e5d6c;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #4e5d6c !important;}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{color:#fff !important;}.landing-header{display:block;margin:60px auto;}.landing-block{background:#2f2f2f !important;padding:5px;}.landing-block .media{margin:30px auto;max-width:450px;}.landing-block .media .media-left{display:inline-block;float:left;width:70px;}.landing-block .media .media-left i.fa{font-size:3em;}.landing-title{font-weight:bold;}.checkbox-custom{margin-top:0 !important;margin-bottom:0 !important;}.tooltip_templates{display:none;}.shadow{-moz-box-shadow:3px 3px 5px 6px #191919;-webkit-box-shadow:3px 3px 5px 6px #191919;box-shadow:3px 3px 5px 6px #191919;}.img-circle{border-radius:50%;}#wrapper{padding-left:0;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled{padding-right:250px;}#sidebar-wrapper{z-index:1000;position:fixed;right:250px;width:0;height:100%;margin-right:-250px;overflow-y:auto;background:#4e5d6c;padding-left:0;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled #sidebar-wrapper{width:500px;}#page-content-wrapper{width:100%;position:absolute;padding:15px;}#wrapper.toggled #page-content-wrapper{position:absolute;margin-left:-250px;}.sidebar-nav{position:absolute;top:0;width:500px;margin:0;padding-left:0;list-style:none;}.sidebar-nav li{text-indent:20px;line-height:40px;}.sidebar-nav li a{display:block;text-decoration:none;color:#999;}.sidebar-nav li a:hover{text-decoration:none;color:#fff;background:rgba(255,255,255,.2);}.sidebar-nav li a:active,.sidebar-nav li a:focus{text-decoration:none;}.sidebar-nav>.sidebar-brand{height:65px;font-size:18px;line-height:60px;}.sidebar-nav>.sidebar-brand a{color:#999;}.sidebar-nav>.sidebar-brand a:hover{color:#fff;background:none;}@media(min-width:768px){#wrapper{padding-right:250px;}#wrapper.toggled{padding-right:0;}#sidebar-wrapper{width:500px;}#wrapper.toggled #sidebar-wrapper{width:0;}#page-content-wrapper{padding:20px;position:relative;}#wrapper.toggled #page-content-wrapper{position:relative;margin-right:0;}}#lightbox{background-color:#808080;filter:alpha(opacity=50);opacity:.5;-moz-opacity:.5;top:0;left:0;z-index:20;height:100%;width:100%;background-repeat:no-repeat;background-position:center;position:absolute;}.list-group-item-dropdown{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#3e3e3e;border:1px solid transparent;}.wizard-heading{text-align:center;}.wizard-img{width:300px;display:block !important;margin:0 auto !important;} \ No newline at end of file diff --git a/Ombi.UI/Content/base.scss b/Ombi.UI/Content/base.scss index aa0417b06..8c0c6e066 100644 --- a/Ombi.UI/Content/base.scss +++ b/Ombi.UI/Content/base.scss @@ -641,4 +641,13 @@ $border-radius: 10px; margin-bottom: -1px; background-color: #3e3e3e; border: 1px solid transparent; +} + +.wizard-heading{ + text-align: center; +} +.wizard-img{ + width: 300px; + display: block $i; + margin: 0 auto $i; } \ No newline at end of file diff --git a/Ombi.UI/Content/images/emby-logo-dark.jpg b/Ombi.UI/Content/images/emby-logo-dark.jpg new file mode 100644 index 000000000..838667b78 Binary files /dev/null and b/Ombi.UI/Content/images/emby-logo-dark.jpg differ diff --git a/Ombi.UI/Content/images/emby-logo.png b/Ombi.UI/Content/images/emby-logo.png new file mode 100644 index 000000000..a6c51ff23 Binary files /dev/null and b/Ombi.UI/Content/images/emby-logo.png differ diff --git a/Ombi.UI/Content/images/logo original.png b/Ombi.UI/Content/images/logo original.png new file mode 100644 index 000000000..c3071e331 Binary files /dev/null and b/Ombi.UI/Content/images/logo original.png differ diff --git a/Ombi.UI/Content/images/logo.png b/Ombi.UI/Content/images/logo.png index 68df0f5cd..560a817e6 100644 Binary files a/Ombi.UI/Content/images/logo.png and b/Ombi.UI/Content/images/logo.png differ diff --git a/Ombi.UI/Content/images/plex-logo-reversed.png b/Ombi.UI/Content/images/plex-logo-reversed.png new file mode 100644 index 000000000..1e754b342 Binary files /dev/null and b/Ombi.UI/Content/images/plex-logo-reversed.png differ diff --git a/Ombi.UI/Content/images/plex-logo.png b/Ombi.UI/Content/images/plex-logo.png new file mode 100644 index 000000000..33355e291 Binary files /dev/null and b/Ombi.UI/Content/images/plex-logo.png differ diff --git a/Ombi.UI/Content/wizard.js b/Ombi.UI/Content/wizard.js index 606a44c5e..8bb81b171 100644 --- a/Ombi.UI/Content/wizard.js +++ b/Ombi.UI/Content/wizard.js @@ -3,9 +3,42 @@ // Step 1 $('#firstNext') .click(function () { - loadArea("plexAuthArea"); + loadArea("mediaApplicationChoice"); }); + + // Plex click + $('#contentBody') + .on("click", "#plexImg", function(e) { + e.preventDefault(); + return loadArea("plexAuthArea"); + }); + + + $('#contentBody') + .on("click", "#embyImg", function (e) { + e.preventDefault(); + return loadArea("embyApiKey"); + }); + + + + $('#contentBody').on('click', '#embyApiKeySave', function (e) { + e.preventDefault(); + $('#spinner').attr("class", "fa fa-spinner fa-spin"); + + var $form = $("#embyAuthForm"); + $.post($form.prop("action"), $form.serialize(), function (response) { + if (response.result === true) { + loadArea("authArea"); + } else { + + $('#spinner').attr("class", "fa fa-times"); + generateNotify(response.message, "warning"); + } + }); + }); + // Step 2 - Get the auth token $('#contentBody').on('click', '#requestToken', function (e) { e.preventDefault(); diff --git a/Ombi.UI/Helpers/HtmlSecurityHelper.cs b/Ombi.UI/Helpers/HtmlSecurityHelper.cs index 1ae398091..8dc2335d3 100644 --- a/Ombi.UI/Helpers/HtmlSecurityHelper.cs +++ b/Ombi.UI/Helpers/HtmlSecurityHelper.cs @@ -72,9 +72,9 @@ namespace Ombi.UI.Helpers return Security.IsLoggedIn(context); } - public static bool IsPlexUser(this HtmlHelpers helper) + public static bool IsExternalUser(this HtmlHelpers helper) { - return Security.IsPlexUser(helper.CurrentUser); + return Security.IsExternalUser(helper.CurrentUser); } public static bool IsNormalUser(this HtmlHelpers helper) { diff --git a/Ombi.UI/Jobs/Scheduler.cs b/Ombi.UI/Jobs/Scheduler.cs index b2c32188a..25151b59f 100644 --- a/Ombi.UI/Jobs/Scheduler.cs +++ b/Ombi.UI/Jobs/Scheduler.cs @@ -55,9 +55,6 @@ namespace Ombi.UI.Jobs private IEnumerable CreateJobs() { - var settingsService = Service.Resolve>(); - var s = settingsService.GetSettings(); - var jobs = new List(); var jobList = new List @@ -76,6 +73,12 @@ namespace Ombi.UI.Jobs JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build(), JobBuilder.Create().WithIdentity("FaultQueueHandler", "Fault").Build(), JobBuilder.Create().WithIdentity("RadarrCacher", "Cache").Build(), + + + JobBuilder.Create().WithIdentity("EmbyEpisodeCacher", "Emby").Build(), + JobBuilder.Create().WithIdentity("EmbyAvailabilityChecker", "Emby").Build(), + JobBuilder.Create().WithIdentity("EmbyContentCacher", "Emby").Build(), + JobBuilder.Create().WithIdentity("EmbyUserChecker", "Emby").Build(), }; jobs.AddRange(jobList); @@ -175,6 +178,22 @@ namespace Ombi.UI.Jobs { s.RadarrCacher = 60; } + if (s.EmbyContentCacher == 0) + { + s.EmbyContentCacher = 60; + } + if (s.EmbyAvailabilityChecker == 0) + { + s.EmbyAvailabilityChecker = 60; + } + if (s.EmbyEpisodeCacher == 0) + { + s.EmbyEpisodeCacher = 11; + } + if (s.EmbyUserChecker == 0) + { + s.EmbyUserChecker = 24; + } var triggers = new List(); @@ -280,6 +299,38 @@ namespace Ombi.UI.Jobs .WithSimpleSchedule(x => x.WithIntervalInHours(s.FaultQueueHandler).RepeatForever()) .Build(); + + //Emby + var embyEpisode = + TriggerBuilder.Create() + .WithIdentity("EmbyEpisodeCacher", "Emby") + //.StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute)) + .StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute)) + .WithSimpleSchedule(x => x.WithIntervalInHours(s.EmbyEpisodeCacher).RepeatForever()) + .Build(); + + var embyContentCacher = + TriggerBuilder.Create() + .WithIdentity("EmbyContentCacher", "Emby") + .StartNow() + .WithSimpleSchedule(x => x.WithIntervalInHours(s.EmbyContentCacher).RepeatForever()) + .Build(); + + var embyAvailabilityChecker = + TriggerBuilder.Create() + .WithIdentity("EmbyAvailabilityChecker", "Emby") + .StartAt(DateBuilder.FutureDate(5, IntervalUnit.Minute)) + .WithSimpleSchedule(x => x.WithIntervalInHours(s.EmbyAvailabilityChecker).RepeatForever()) + .Build(); + + var embyUserChecker = + TriggerBuilder.Create() + .WithIdentity("EmbyUserChecker", "Emby") + //.StartNow() + .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Minute)) + .WithSimpleSchedule(x => x.WithIntervalInHours(s.EmbyUserChecker).RepeatForever()) + .Build(); + triggers.Add(rencentlyAdded); triggers.Add(plexAvailabilityChecker); triggers.Add(srCacher); @@ -294,6 +345,10 @@ namespace Ombi.UI.Jobs triggers.Add(plexCacher); triggers.Add(plexUserChecker); triggers.Add(radarrCacher); + triggers.Add(embyEpisode); + triggers.Add(embyAvailabilityChecker); + triggers.Add(embyContentCacher); + triggers.Add(embyUserChecker); return triggers; } diff --git a/Ombi.UI/Models/ScheduledJobsViewModel.cs b/Ombi.UI/Models/ScheduledJobsViewModel.cs index df1a6ef2f..62b186216 100644 --- a/Ombi.UI/Models/ScheduledJobsViewModel.cs +++ b/Ombi.UI/Models/ScheduledJobsViewModel.cs @@ -33,6 +33,8 @@ namespace Ombi.UI.Models { public class ScheduledJobsViewModel : ScheduledJobsSettings { + public bool Emby { get; set; } + public bool Plex { get; set; } public Dictionary JobRecorder { get; set; } } } \ No newline at end of file diff --git a/Ombi.UI/Modules/Admin/AdminModule.cs b/Ombi.UI/Modules/Admin/AdminModule.cs index 97a610441..a83ff8bd1 100644 --- a/Ombi.UI/Modules/Admin/AdminModule.cs +++ b/Ombi.UI/Modules/Admin/AdminModule.cs @@ -97,6 +97,8 @@ namespace Ombi.UI.Modules.Admin private IDiscordApi DiscordApi { get; } private ISettingsService RadarrSettings { get; } private IRadarrApi RadarrApi { get; } + private ISettingsService EmbySettings { get; } + private IEmbyApi EmbyApi { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); public AdminModule(ISettingsService prService, @@ -124,7 +126,8 @@ namespace Ombi.UI.Modules.Admin ISettingsService notifyService, IRecentlyAdded recentlyAdded, ISettingsService watcherSettings , ISettingsService discord, - IDiscordApi discordapi, ISettingsService settings, IRadarrApi radarrApi + IDiscordApi discordapi, ISettingsService settings, IRadarrApi radarrApi, + ISettingsService embySettings, IEmbyApi emby , ISecurityExtensions security) : base("admin", prService, security) { PrService = prService; @@ -160,7 +163,9 @@ namespace Ombi.UI.Modules.Admin DiscordApi = discordapi; RadarrSettings = settings; RadarrApi = radarrApi; - + EmbyApi = emby; + EmbySettings = embySettings; + Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); Get["/"] = _ => Admin(); @@ -180,6 +185,10 @@ namespace Ombi.UI.Modules.Admin Get["/plex"] = _ => Plex(); Post["/plex", true] = async (x, ct) => await SavePlex(); + Get["/emby", true] = async (x, ct) => await Emby(); + Post["/emby", true] = async (x, ct) => await SaveEmby(); + + Get["/sonarr"] = _ => Sonarr(); Post["/sonarr"] = _ => SaveSonarr(); Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); @@ -438,6 +447,21 @@ namespace Ombi.UI.Modules.Admin return Response.AsJson(valid.SendJsonError()); } + + if (plexSettings.Enable) + { + var embySettings = await EmbySettings.GetSettingsAsync(); + if (embySettings.Enable) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Emby is enabled, we cannot enable Plex and Emby" + }); + } + } + if (string.IsNullOrEmpty(plexSettings.MachineIdentifier)) { //Lookup identifier @@ -453,6 +477,49 @@ namespace Ombi.UI.Modules.Admin : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } + private async Task Emby() + { + var settings = await EmbySettings.GetSettingsAsync(); + + return View["Emby", settings]; + } + + private async Task SaveEmby() + { + var emby = this.Bind(); + var valid = this.Validate(emby); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + if (emby.Enable) + { + var plexSettings = await PlexService.GetSettingsAsync(); + if (plexSettings.Enable) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Plex is enabled, we cannot enable Plex and Emby" + }); + } + } + + // Get the users + var users = EmbyApi.GetUsers(emby.FullUri, emby.ApiKey); + // Find admin + var admin = users.FirstOrDefault(x => x.Policy.IsAdministrator); + emby.AdministratorId = admin?.Id; + + var result = await EmbySettings.SaveSettingsAsync(emby); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Emby!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + private Negotiator Sonarr() { var settings = SonarrService.GetSettings(); @@ -1063,6 +1130,8 @@ namespace Ombi.UI.Modules.Admin { var s = await ScheduledJobSettings.GetSettingsAsync(); var allJobs = await JobRecorder.GetJobsAsync(); + var emby = await EmbySettings.GetSettingsAsync(); + var plex = await PlexService.GetSettingsAsync(); var dict = new Dictionary(); @@ -1083,6 +1152,8 @@ namespace Ombi.UI.Modules.Admin var model = new ScheduledJobsViewModel { + Emby = emby.Enable, + Plex = plex.Enable, CouchPotatoCacher = s.CouchPotatoCacher, PlexAvailabilityChecker = s.PlexAvailabilityChecker, SickRageCacher = s.SickRageCacher, diff --git a/Ombi.UI/Modules/Admin/ScheduledJobsRunnerModule.cs b/Ombi.UI/Modules/Admin/ScheduledJobsRunnerModule.cs index a21ebd16a..a08bd056d 100644 --- a/Ombi.UI/Modules/Admin/ScheduledJobsRunnerModule.cs +++ b/Ombi.UI/Modules/Admin/ScheduledJobsRunnerModule.cs @@ -33,6 +33,7 @@ using Ombi.Core.SettingModels; using Ombi.Helpers.Permissions; using Ombi.Services.Interfaces; using Ombi.Services.Jobs; +using Ombi.Services.Jobs.Interfaces; using Ombi.UI.Models; using ISecurityExtensions = Ombi.Core.ISecurityExtensions; @@ -44,7 +45,8 @@ namespace Ombi.UI.Modules.Admin ISecurityExtensions security, IPlexContentCacher contentCacher, ISonarrCacher sonarrCacher, IWatcherCacher watcherCacher, IRadarrCacher radarrCacher, ICouchPotatoCacher cpCacher, IStoreBackup store, ISickRageCacher srCacher, IAvailabilityChecker plexChceker, IStoreCleanup cleanup, IUserRequestLimitResetter requestLimit, IPlexEpisodeCacher episodeCacher, IRecentlyAdded recentlyAdded, - IFaultQueueHandler faultQueueHandler, IPlexUserChecker plexUserChecker) : base("admin", settingsService, security) + IFaultQueueHandler faultQueueHandler, IPlexUserChecker plexUserChecker, IEmbyAvailabilityChecker embyAvailabilityChecker, IEmbyEpisodeCacher embyEpisode, + IEmbyContentCacher embyContentCacher, IEmbyUserChecker embyUser) : base("admin", settingsService, security) { Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); @@ -62,6 +64,10 @@ namespace Ombi.UI.Modules.Admin RecentlyAdded = recentlyAdded; FaultQueueHandler = faultQueueHandler; PlexUserChecker = plexUserChecker; + EmbyAvailabilityChecker = embyAvailabilityChecker; + EmbyContentCacher = embyContentCacher; + EmbyEpisodeCacher = embyEpisode; + EmbyUserChecker = embyUser; Post["/schedulerun", true] = async (x, ct) => await ScheduleRun((string)Request.Form.key); } @@ -80,6 +86,10 @@ namespace Ombi.UI.Modules.Admin private IRecentlyAdded RecentlyAdded { get; } private IFaultQueueHandler FaultQueueHandler { get; } private IPlexUserChecker PlexUserChecker { get; } + private IEmbyAvailabilityChecker EmbyAvailabilityChecker { get; } + private IEmbyContentCacher EmbyContentCacher { get; } + private IEmbyEpisodeCacher EmbyEpisodeCacher { get; } + private IEmbyUserChecker EmbyUserChecker { get; } private async Task ScheduleRun(string key) @@ -142,6 +152,22 @@ namespace Ombi.UI.Modules.Admin { RequestLimit.Start(); } + if (key.Equals(JobNames.EmbyEpisodeCacher, StringComparison.CurrentCultureIgnoreCase)) + { + EmbyEpisodeCacher.Start(); + } + if (key.Equals(JobNames.EmbyCacher, StringComparison.CurrentCultureIgnoreCase)) + { + EmbyContentCacher.CacheContent(); + } + if (key.Equals(JobNames.EmbyChecker, StringComparison.CurrentCultureIgnoreCase)) + { + EmbyAvailabilityChecker.CheckAndUpdateAll(); + } + if (key.Equals(JobNames.EmbyUserChecker, StringComparison.CurrentCultureIgnoreCase)) + { + EmbyUserChecker.Start(); + } return Response.AsJson(new JsonResponseModel { Result = true }); diff --git a/Ombi.UI/Modules/ApplicationTesterModule.cs b/Ombi.UI/Modules/ApplicationTesterModule.cs index a7608f547..5083239a9 100644 --- a/Ombi.UI/Modules/ApplicationTesterModule.cs +++ b/Ombi.UI/Modules/ApplicationTesterModule.cs @@ -46,7 +46,7 @@ namespace Ombi.UI.Modules public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPlexApi plexApi, ISickRageApi srApi, IHeadphonesApi hpApi, ISettingsService pr, ISecurityExtensions security, - IWatcherApi watcherApi, IRadarrApi radarrApi) : base("test", pr, security) + IWatcherApi watcherApi, IRadarrApi radarrApi, IEmbyApi emby) : base("test", pr, security) { this.RequiresAuthentication(); @@ -57,6 +57,7 @@ namespace Ombi.UI.Modules HeadphonesApi = hpApi; WatcherApi = watcherApi; RadarrApi = radarrApi; + Emby = emby; Post["/cp"] = _ => CouchPotatoTest(); Post["/sonarr"] = _ => SonarrTest(); @@ -66,6 +67,7 @@ namespace Ombi.UI.Modules Post["/headphones"] = _ => HeadphonesTest(); Post["/plexdb"] = _ => TestPlexDb(); Post["/watcher"] = _ => WatcherTest(); + Post["/emby"] = _ => EmbyTest(); } private static readonly Logger Log = LogManager.GetCurrentClassLogger(); @@ -76,6 +78,7 @@ namespace Ombi.UI.Modules private IHeadphonesApi HeadphonesApi { get; } private IWatcherApi WatcherApi { get; } private IRadarrApi RadarrApi { get; } + private IEmbyApi Emby { get; set; } private Response CouchPotatoTest() { @@ -213,7 +216,7 @@ namespace Ombi.UI.Modules : Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Plex, please check your settings." }); } - catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them. + catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them. { Log.Warn("Exception thrown when attempting to get Plex's status: "); Log.Warn(e); @@ -225,6 +228,35 @@ namespace Ombi.UI.Modules return Response.AsJson(new JsonResponseModel { Result = false, Message = message }); } } + private Response EmbyTest() + { + var emby = this.Bind(); + var valid = this.Validate(emby); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + try + { + var status = Emby.GetUsers(emby?.FullUri, emby?.ApiKey); + return status != null + ? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Emby successfully!" }) + : Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Emby, please check your settings." }); + + } + catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them. + { + Log.Warn("Exception thrown when attempting to get Emby's users: "); + Log.Warn(e); + var message = $"Could not connect to Emby, please check your settings. Exception Message: {e.Message}"; + if (e.InnerException != null) + { + message = $"Could not connect to Emby, please check your settings. Exception Message: {e.InnerException.Message}"; + } + return Response.AsJson(new JsonResponseModel { Result = false, Message = message }); + } + } private Response SickRageTest() { diff --git a/Ombi.UI/Modules/RequestsModule.cs b/Ombi.UI/Modules/RequestsModule.cs index 699b19b67..193d11c33 100644 --- a/Ombi.UI/Modules/RequestsModule.cs +++ b/Ombi.UI/Modules/RequestsModule.cs @@ -65,7 +65,7 @@ namespace Ombi.UI.Modules ISickRageApi sickRageApi, ICacheProvider cache, IAnalytics an, - INotificationEngine engine, + IPlexNotificationEngine engine, ISecurityExtensions security, ISettingsService customSettings) : base("requests", prSettings, security) { @@ -438,8 +438,7 @@ namespace Ombi.UI.Modules originalRequest.Available = available; var result = await Service.UpdateRequestAsync(originalRequest); - var plexService = await PlexSettings.GetSettingsAsync(); - await NotificationEngine.NotifyUsers(originalRequest, plexService.PlexAuthToken, available ? NotificationType.RequestAvailable : NotificationType.RequestDeclined); + await NotificationEngine.NotifyUsers(originalRequest, available ? NotificationType.RequestAvailable : NotificationType.RequestDeclined); return Response.AsJson(result ? new { Result = true, Available = available, Message = string.Empty } : new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" }); diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 4aa29c316..20b43097d 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -50,9 +50,11 @@ using Ombi.Helpers; using Ombi.Helpers.Analytics; using Ombi.Helpers.Permissions; using Ombi.Services.Interfaces; +using Ombi.Services.Jobs; using Ombi.Services.Notification; using Ombi.Store; using Ombi.Store.Models; +using Ombi.Store.Models.Emby; using Ombi.Store.Models.Plex; using Ombi.Store.Repository; using Ombi.UI.Helpers; @@ -68,7 +70,7 @@ namespace Ombi.UI.Modules public class SearchModule : BaseAuthModule { public SearchModule(ICacheProvider cache, - ISettingsService prSettings, IAvailabilityChecker checker, + ISettingsService prSettings, IAvailabilityChecker plexChecker, IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, ISettingsService sickRageService, ISickRageApi srApi, INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, @@ -77,7 +79,8 @@ namespace Ombi.UI.Modules ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, IIssueService issue, IAnalytics a, IRepository rl, ITransientFaultQueue tfQueue, IRepository content, - ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher, ITraktApi traktApi, ISettingsService cus) + ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher, ITraktApi traktApi, ISettingsService cus, + IEmbyAvailabilityChecker embyChecker, IRepository embyContent, ISettingsService embySettings) : base("search", prSettings, security) { Auth = auth; @@ -86,7 +89,7 @@ namespace Ombi.UI.Modules PrService = prSettings; MovieApi = new TheMovieDbApi(); Cache = cache; - Checker = checker; + PlexChecker = plexChecker; CpCacher = cpCacher; SonarrCacher = sonarrCacher; SickRageCacher = sickRageCacher; @@ -112,6 +115,9 @@ namespace Ombi.UI.Modules RadarrCacher = radarrCacher; TraktApi = traktApi; CustomizationSettings = cus; + EmbyChecker = embyChecker; + EmbyContentRepository = embyContent; + EmbySettings = embySettings; Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad(); @@ -143,6 +149,7 @@ namespace Ombi.UI.Modules private IWatcherCacher WatcherCacher { get; } private IMovieSender MovieSender { get; } private IRepository PlexContentRepository { get; } + private IRepository EmbyContentRepository { get; } private TvMazeApi TvApi { get; } private IPlexApi PlexApi { get; } private TheMovieDbApi MovieApi { get; } @@ -152,13 +159,15 @@ namespace Ombi.UI.Modules private IRequestService RequestService { get; } private ICacheProvider Cache { get; } private ISettingsService Auth { get; } + private ISettingsService EmbySettings { get; } private ISettingsService PlexService { get; } private ISettingsService PrService { get; } private ISettingsService SonarrService { get; } private ISettingsService SickRageService { get; } private ISettingsService HeadphonesService { get; } private ISettingsService EmailNotificationSettings { get; } - private IAvailabilityChecker Checker { get; } + private IAvailabilityChecker PlexChecker { get; } + private IEmbyAvailabilityChecker EmbyChecker { get; } private ICouchPotatoCacher CpCacher { get; } private ISonarrCacher SonarrCacher { get; } private ISickRageCacher SickRageCacher { get; } @@ -269,8 +278,7 @@ namespace Ombi.UI.Modules var cpCached = CpCacher.QueuedIds(); var watcherCached = WatcherCacher.QueuedIds(); var radarrCached = RadarrCacher.QueuedIds(); - var content = PlexContentRepository.GetAll(); - var plexMovies = Checker.GetPlexMovies(content); + var viewMovies = new List(); var counter = 0; foreach (var movie in apiMovies) @@ -292,8 +300,7 @@ namespace Ombi.UI.Modules VoteAverage = movie.VoteAverage, VoteCount = movie.VoteCount }; - - var imdbId = string.Empty; + if (counter <= 5) // Let's only do it for the first 5 items { var movieInfo = MovieApi.GetMovieInformationWithVideos(movie.Id); @@ -313,14 +320,35 @@ namespace Ombi.UI.Modules counter++; } - var canSee = CanUserSeeThisRequest(viewMovie.Id, Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests), dbMovies); - var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString(), - imdbId); - if (plexMovie != null) + + var plexSettings = await PlexService.GetSettingsAsync(); + var embySettings = await EmbySettings.GetSettingsAsync(); + if (plexSettings.Enable) { - viewMovie.Available = true; - viewMovie.PlexUrl = plexMovie.Url; + var content = PlexContentRepository.GetAll(); + var plexMovies = PlexChecker.GetPlexMovies(content); + + var plexMovie = PlexChecker.GetMovie(plexMovies.ToArray(), movie.Title, + movie.ReleaseDate?.Year.ToString(), + viewMovie.ImdbId); + if (plexMovie != null) + { + viewMovie.Available = true; + viewMovie.PlexUrl = plexMovie.Url; + } + } + if (embySettings.Enable) + { + var embyContent = EmbyContentRepository.GetAll(); + var embyMovies = EmbyChecker.GetEmbyMovies(embyContent); + + var embyMovie = EmbyChecker.GetMovie(embyMovies.ToArray(), movie.Title, + movie.ReleaseDate?.Year.ToString(), viewMovie.ImdbId); + if (embyMovie != null) + { + viewMovie.Available = true; + } } else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db { @@ -335,7 +363,7 @@ namespace Ombi.UI.Modules viewMovie.Approved = true; viewMovie.Requested = true; } - else if(watcherCached.Contains(imdbId) && canSee) // compare to the watcher db + else if (watcherCached.Contains(viewMovie.ImdbId) && canSee) // compare to the watcher db { viewMovie.Approved = true; viewMovie.Requested = true; @@ -507,7 +535,7 @@ namespace Ombi.UI.Modules var sonarrCached = SonarrCacher.QueuedIds().ToList(); var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays var content = PlexContentRepository.GetAll(); - var plexTvShows = Checker.GetPlexTvShows(content).ToList(); + var plexTvShows = PlexChecker.GetPlexTvShows(content).ToList(); foreach (var show in shows) { @@ -516,7 +544,7 @@ namespace Ombi.UI.Modules providerId = show.Id.ToString(); } - var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), show.SeriesName, show.FirstAired?.Substring(0, 4), + var plexShow = PlexChecker.GetTvShow(plexTvShows.ToArray(), show.SeriesName, show.FirstAired?.Substring(0, 4), providerId); if (plexShow != null) { @@ -571,7 +599,7 @@ namespace Ombi.UI.Modules var sonarrCached = SonarrCacher.QueuedIds(); var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays var content = PlexContentRepository.GetAll(); - var plexTvShows = Checker.GetPlexTvShows(content); + var plexTvShows = PlexChecker.GetPlexTvShows(content); var viewTv = new List(); foreach (var t in apiTv) @@ -611,7 +639,7 @@ namespace Ombi.UI.Modules providerId = viewT.Id.ToString(); } - var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), + var plexShow = PlexChecker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId); if (plexShow != null) { @@ -658,7 +686,7 @@ namespace Ombi.UI.Modules var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId); var content = PlexContentRepository.GetAll(); - var plexAlbums = Checker.GetPlexAlbums(content); + var plexAlbums = PlexChecker.GetPlexAlbums(content); var viewAlbum = new List(); foreach (var a in apiAlbums) @@ -678,7 +706,7 @@ namespace Ombi.UI.Modules DateTime release; DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); var artist = a.ArtistCredit?.FirstOrDefault()?.artist; - var plexAlbum = Checker.GetAlbum(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name); + var plexAlbum = PlexChecker.GetAlbum(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name); if (plexAlbum != null) { viewA.Available = true; @@ -760,8 +788,8 @@ namespace Ombi.UI.Modules { var content = PlexContentRepository.GetAll(); - var movies = Checker.GetPlexMovies(content); - if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) + var movies = PlexChecker.GetPlexMovies(content); + if (PlexChecker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) { return Response.AsJson(new JsonResponseModel @@ -1043,67 +1071,136 @@ namespace Ombi.UI.Modules try { - var content = PlexContentRepository.GetAll(); - var shows = Checker.GetPlexTvShows(content); - var providerId = string.Empty; var plexSettings = await PlexService.GetSettingsAsync(); - if (plexSettings.AdvancedSearch) - { - providerId = showId.ToString(); - } - if (episodeRequest) + if (plexSettings.Enable) { - var cachedEpisodesTask = await Checker.GetEpisodes(); - var cachedEpisodes = cachedEpisodesTask.ToList(); - foreach (var d in difference) // difference is from an existing request + var content = PlexContentRepository.GetAll(); + var shows = PlexChecker.GetPlexTvShows(content); + + var providerId = string.Empty; + if (plexSettings.AdvancedSearch) { - if ( - cachedEpisodes.Any( - x => - x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && - x.ProviderId == providerId)) + providerId = showId.ToString(); + } + if (episodeRequest) + { + var cachedEpisodesTask = await PlexChecker.GetEpisodes(); + var cachedEpisodes = cachedEpisodesTask.ToList(); + foreach (var d in difference) // difference is from an existing request + { + if ( + cachedEpisodes.Any( + x => + x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && + x.ProviderId == providerId)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = + $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" + }); + } + } + + var diff = await GetEpisodeRequestDifference(showId, model); + model.Episodes = diff.ToList(); + } + else + { + if (plexSettings.EnableTvEpisodeSearching) + { + foreach (var s in showInfo.Season) + { + var result = PlexChecker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, + s.EpisodeNumber); + if (result) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); + } + } + } + else if (PlexChecker.IsTvShowAvailable(shows.ToArray(), showInfo.name, + showInfo.premiered?.Substring(0, 4), + providerId, model.SeasonList)) { return Response.AsJson(new JsonResponseModel { Result = false, - Message = - $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" }); } } - - var diff = await GetEpisodeRequestDifference(showId, model); - model.Episodes = diff.ToList(); } - else + var embySettings = await EmbySettings.GetSettingsAsync(); + if (embySettings.Enable) { - if (plexSettings.EnableTvEpisodeSearching) + var embyContent = EmbyContentRepository.GetAll(); + var embyMovies = EmbyChecker.GetEmbyTvShows(embyContent); + var providerId = showId.ToString(); + if (episodeRequest) { - foreach (var s in showInfo.Season) + var cachedEpisodesTask = await EmbyChecker.GetEpisodes(); + var cachedEpisodes = cachedEpisodesTask.ToList(); + foreach (var d in difference) // difference is from an existing request { - var result = Checker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, s.EpisodeNumber); - if (result) + if ( + cachedEpisodes.Any( + x => + x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && + x.ProviderId == providerId)) { return Response.AsJson(new JsonResponseModel { Result = false, - Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + Message = + $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" }); } } + + var diff = await GetEpisodeRequestDifference(showId, model); + model.Episodes = diff.ToList(); } - else if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), - providerId, model.SeasonList)) + else { - return - Response.AsJson(new JsonResponseModel + if (embySettings.EnableEpisodeSearching) + { + foreach (var s in showInfo.Season) { - Result = false, - Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" - }); - } + var result = EmbyChecker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, + s.EpisodeNumber); + if (result) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} is already in Emby!" + }); + } + } + } + else if (EmbyChecker.IsTvShowAvailable(embyMovies.ToArray(), showInfo.name, + showInfo.premiered?.Substring(0, 4), + providerId, model.SeasonList)) + { + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} is already in Emby!" + }); + } + } } } catch (Exception) @@ -1261,8 +1358,8 @@ namespace Ombi.UI.Modules var content = PlexContentRepository.GetAll(); - var albums = Checker.GetPlexAlbums(content); - var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), + var albums = PlexChecker.GetPlexAlbums(content); + var alreadyInPlex = PlexChecker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), artist.name); if (alreadyInPlex) @@ -1390,7 +1487,8 @@ namespace Ombi.UI.Modules var existingRequest = requests.FirstOrDefault(x => x.Type == RequestType.TvShow && x.TvDbId == providerId.ToString()); var show = await Task.Run(() => TvApi.ShowLookupByTheTvDbId(providerId)); - var tvMaxeEpisodes = await Task.Run(() => TvApi.EpisodeLookup(show.id)); + var tvMazeEpisodesTask = await Task.Run(() => TvApi.EpisodeLookup(show.id)); + var tvMazeEpisodes = tvMazeEpisodesTask.ToList(); var sonarrEpisodes = new List(); if (sonarrEnabled) @@ -1400,26 +1498,59 @@ namespace Ombi.UI.Modules sonarrEpisodes = sonarrEp?.ToList() ?? new List(); } - var plexCacheTask = await Checker.GetEpisodes(providerId); - var plexCache = plexCacheTask.ToList(); - foreach (var ep in tvMaxeEpisodes) + var plexSettings = await PlexService.GetSettingsAsync(); + if (plexSettings.Enable) { - var requested = existingRequest?.Episodes - .Any(episodesModel => - ep.number == episodesModel.EpisodeNumber && ep.season == episodesModel.SeasonNumber) ?? false; + var plexCacheTask = await PlexChecker.GetEpisodes(providerId); + var plexCache = plexCacheTask.ToList(); + foreach (var ep in tvMazeEpisodes) + { + var requested = existingRequest?.Episodes + .Any(episodesModel => + ep.number == episodesModel.EpisodeNumber && + ep.season == episodesModel.SeasonNumber) ?? false; - var alreadyInPlex = plexCache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season); - var inSonarr = sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile); + var alreadyInPlex = plexCache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season); + var inSonarr = + sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile); - model.Add(new EpisodeListViewModel + model.Add(new EpisodeListViewModel + { + Id = show.id, + SeasonNumber = ep.season, + EpisodeNumber = ep.number, + Requested = requested || alreadyInPlex || inSonarr, + Name = ep.name, + EpisodeId = ep.id + }); + } + } + var embySettings = await EmbySettings.GetSettingsAsync(); + if (embySettings.Enable) + { + var embyCacheTask = await EmbyChecker.GetEpisodes(providerId); + var cache = embyCacheTask.ToList(); + foreach (var ep in tvMazeEpisodes) { - Id = show.id, - SeasonNumber = ep.season, - EpisodeNumber = ep.number, - Requested = requested || alreadyInPlex || inSonarr, - Name = ep.name, - EpisodeId = ep.id - }); + var requested = existingRequest?.Episodes + .Any(episodesModel => + ep.number == episodesModel.EpisodeNumber && + ep.season == episodesModel.SeasonNumber) ?? false; + + var alreadyInEmby = cache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season); + var inSonarr = + sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile); + + model.Add(new EpisodeListViewModel + { + Id = show.id, + SeasonNumber = ep.season, + EpisodeNumber = ep.number, + Requested = requested || alreadyInEmby || inSonarr, + Name = ep.name, + EpisodeId = ep.id + }); + } } return model; @@ -1651,3 +1782,4 @@ namespace Ombi.UI.Modules } } } + diff --git a/Ombi.UI/Modules/UserLoginModule.cs b/Ombi.UI/Modules/UserLoginModule.cs index 40fea430f..fd3dea0b3 100644 --- a/Ombi.UI/Modules/UserLoginModule.cs +++ b/Ombi.UI/Modules/UserLoginModule.cs @@ -34,7 +34,9 @@ using Nancy; using Nancy.Extensions; using Nancy.Linker; 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; @@ -44,6 +46,8 @@ using Ombi.Helpers.Analytics; using Ombi.Helpers.Permissions; using Ombi.Store; using Ombi.Store.Models; +using Ombi.Store.Models.Emby; +using Ombi.Store.Models.Plex; using Ombi.Store.Repository; using Ombi.UI.Authentication; using ISecurityExtensions = Ombi.Core.ISecurityExtensions; @@ -54,8 +58,8 @@ namespace Ombi.UI.Modules public class UserLoginModule : BaseModule { public UserLoginModule(ISettingsService auth, IPlexApi api, ISettingsService plexSettings, ISettingsService pr, - ISettingsService lp, IAnalytics a, IResourceLinker linker, IRepository userLogins, IPlexUserRepository plexUsers, ICustomUserMapper custom, - ISecurityExtensions security, ISettingsService userManagementSettings) + ISettingsService lp, IAnalytics a, IResourceLinker linker, IRepository userLogins, IExternalUserRepository plexUsers, ICustomUserMapper custom, + ISecurityExtensions security, ISettingsService userManagementSettings, IEmbyApi embyApi, ISettingsService emby, IExternalUserRepository embyU) : base("userlogin", pr, security) { AuthService = auth; @@ -68,45 +72,9 @@ namespace Ombi.UI.Modules PlexUserRepository = plexUsers; CustomUserMapper = custom; UserManagementSettings = userManagementSettings; - - //Get["UserLoginIndex", "/", true] = async (x, ct) => - //{ - // if (Request.Query["landing"] == null) - // { - // var s = await LandingPageSettings.GetSettingsAsync(); - // if (s.Enabled) - // { - // if (s.BeforeLogin) // Before login - // { - // if (string.IsNullOrEmpty(Username)) - // { - // // They are not logged in - // return - // Context.GetRedirect(Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString()); - // } - // return Context.GetRedirect(Linker.BuildRelativeUri(Context, "SearchIndex").ToString()); - // } - - // // After login - // if (string.IsNullOrEmpty(Username)) - // { - // // Not logged in yet - // return Context.GetRedirect(Linker.BuildRelativeUri(Context, "UserLoginIndex").ToString() + "?landing"); - // } - // // Send them to landing - // var landingUrl = Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString(); - // return Context.GetRedirect(landingUrl); - // } - // } - - // if (!string.IsNullOrEmpty(Username) || IsAdmin) - // { - // var url = Linker.BuildRelativeUri(Context, "SearchIndex").ToString(); - // return Response.AsRedirect(url); - // } - // var settings = await AuthService.GetSettingsAsync(); - // return View["Index", settings]; - //}; + EmbySettings = emby; + EmbyApi = embyApi; + EmbyUserRepository = embyU; Post["/", true] = async (x, ct) => await LoginUser(); Get["/logout"] = x => Logout(); @@ -157,11 +125,14 @@ namespace Ombi.UI.Modules private ISettingsService AuthService { get; } private ISettingsService LandingPageSettings { get; } private ISettingsService PlexSettings { get; } + private ISettingsService EmbySettings { get; } private IPlexApi Api { get; } + private IEmbyApi EmbyApi { get; } private IResourceLinker Linker { get; } private IAnalytics Analytics { get; } private IRepository UserLogins { get; } - private IPlexUserRepository PlexUserRepository { get; } + private IExternalUserRepository PlexUserRepository { get; } + private IExternalUserRepository EmbyUserRepository { get; } private ICustomUserMapper CustomUserMapper { get; } private ISettingsService UserManagementSettings { get; } @@ -180,41 +151,77 @@ namespace Ombi.UI.Modules } var plexSettings = await PlexSettings.GetSettingsAsync(); + var embySettings = await EmbySettings.GetSettingsAsync(); var authenticated = false; var isOwner = false; var userId = string.Empty; + EmbyUser embyUser = null; - if (settings.UserAuthentication) // Check against the users in Plex + if (plexSettings.Enable) { - Log.Debug("Need to auth"); - authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken); - if (authenticated) + if (settings.UserAuthentication) // Check against the users in Plex { - userId = GetUserIdIsInPlexFriends(username, plexSettings.PlexAuthToken); + Log.Debug("Need to auth"); + authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken); + if (authenticated) + { + userId = GetUserIdIsInPlexFriends(username, plexSettings.PlexAuthToken); + } + if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, username)) + { + Log.Debug("User is the account owner"); + authenticated = true; + isOwner = true; + userId = GetOwnerId(plexSettings.PlexAuthToken, username); + } + UsersModel dbUser = await IsDbuser(username); + if (dbUser != null) // in the db? + { + var perms = (Permissions) dbUser.Permissions; + authenticated = true; + isOwner = perms.HasFlag(Permissions.Administrator); + userId = dbUser.UserGuid; + } + Log.Debug("Friends list result = {0}", authenticated); } - if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, username)) + else if (!settings.UserAuthentication) // No auth, let them pass! { - Log.Debug("User is the account owner"); authenticated = true; - isOwner = true; - userId = GetOwnerId(plexSettings.PlexAuthToken, username); } - UsersModel dbUser = await IsDbuser(username); - if (dbUser != null) // in the db? + } + if (embySettings.Enable) + { + if (settings.UserAuthentication) // Check against the users in Plex + { + Log.Debug("Need to auth"); + authenticated = CheckIfEmbyUser(username, embySettings); + if (authenticated) + { + embyUser = GetEmbyUser(username, embySettings); + userId = embyUser?.Id; + } + if (embyUser?.Policy?.IsAdministrator ?? false) + { + Log.Debug("User is the account owner"); + authenticated = true; + isOwner = true; + } + UsersModel dbUser = await IsDbuser(username); + if (dbUser != null) // in the db? + { + var perms = (Permissions)dbUser.Permissions; + authenticated = true; + isOwner = perms.HasFlag(Permissions.Administrator); + userId = dbUser.UserGuid; + } + Log.Debug("Friends list result = {0}", authenticated); + } + else if (!settings.UserAuthentication) // No auth, let them pass! { - var perms = (Permissions)dbUser.Permissions; authenticated = true; - isOwner = perms.HasFlag(Permissions.Administrator); - userId = dbUser.UserGuid; } - Log.Debug("Friends list result = {0}", authenticated); } - else if (!settings.UserAuthentication) // No auth, let them pass! - { - authenticated = true; - } - if (settings.UsePassword || isOwner || Security.HasPermissions(username, Permissions.Administrator)) { Session[SessionKeys.UserLoginName] = username; @@ -230,7 +237,7 @@ namespace Ombi.UI.Modules { return Response.AsJson(new { result = false, message = Resources.UI.UserLogin_IncorrectUserPass }); } - var result = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner); + var result = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner, plexSettings.Enable, embySettings.Enable); var landingSettings = await LandingPageSettings.GetSettingsAsync(); @@ -292,26 +299,54 @@ namespace Ombi.UI.Modules var userId = string.Empty; var plexSettings = await PlexSettings.GetSettingsAsync(); + var embySettings = await EmbySettings.GetSettingsAsync(); - if (settings.UserAuthentication) // Authenticate with Plex + if (plexSettings.Enable) { - Log.Debug("Need to auth and also provide pass"); - var signedIn = (PlexAuthentication)Api.SignIn(username, password); - if (signedIn.user?.authentication_token != null) + if (settings.UserAuthentication) // Authenticate with Plex { - Log.Debug("Correct credentials, checking if the user is account owner or in the friends list"); - if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, signedIn.user?.username)) + Log.Debug("Need to auth and also provide pass"); + var signedIn = (PlexAuthentication) Api.SignIn(username, password); + if (signedIn.user?.authentication_token != null) { - Log.Debug("User is the account owner"); - authenticated = true; - isOwner = true; + Log.Debug("Correct credentials, checking if the user is account owner or in the friends list"); + if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, signedIn.user?.username)) + { + Log.Debug("User is the account owner"); + authenticated = true; + isOwner = true; + } + else + { + authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken); + Log.Debug("Friends list result = {0}", authenticated); + } + userId = signedIn.user.uuid; } - else + } + } + if (embySettings.Enable) + { + if (settings.UserAuthentication) // Authenticate with Plex + { + Log.Debug("Need to auth and also provide pass"); + var signedIn = (EmbyUser)EmbyApi.LogIn(username, password, embySettings.ApiKey, embySettings.FullUri); + if (signedIn != null) { - authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken); - Log.Debug("Friends list result = {0}", authenticated); + Log.Debug("Correct credentials, checking if the user is account owner or in the friends list"); + if (signedIn?.Policy?.IsAdministrator ?? false) + { + Log.Debug("User is the account owner"); + authenticated = true; + isOwner = true; + } + else + { + authenticated = CheckIfEmbyUser(username, embySettings); + Log.Debug("Friends list result = {0}", authenticated); + } + userId = signedIn?.Id; } - userId = signedIn.user.uuid; } } @@ -331,7 +366,7 @@ namespace Ombi.UI.Modules } - var m = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner); + var m = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner, plexSettings.Enable, embySettings.Enable); var landingSettings = await LandingPageSettings.GetSettingsAsync(); @@ -553,59 +588,47 @@ namespace Ombi.UI.Modules public string UserId { get; set; } } - private async Task AuthenticationSetup(string userId, string username, int dateTimeOffset, Guid loginGuid, bool isOwner) + private async Task AuthenticationSetup(string userId, string username, int dateTimeOffset, Guid loginGuid, bool isOwner, bool plex, bool emby) { var m = new LoginModel(); var settings = await AuthService.GetSettingsAsync(); var localUsers = await CustomUserMapper.GetUsersAsync(); var plexLocalUsers = await PlexUserRepository.GetAllAsync(); + var embyLocalUsers = await EmbyUserRepository.GetAllAsync(); + + var localUser = false; + - UserLogins.Insert(new UserLogins { UserId = userId, Type = UserType.PlexUser, LastLoggedIn = DateTime.UtcNow }); Log.Debug("We are authenticated! Setting session."); // Add to the session (Used in the BaseModules) Session[SessionKeys.UsernameKey] = username; Session[SessionKeys.ClientDateTimeOffsetKey] = dateTimeOffset; - var plexLocal = plexLocalUsers.FirstOrDefault(x => x.Username == username); - if (plexLocal != null) + if (plex) { - loginGuid = Guid.Parse(plexLocal.LoginId); + var plexLocal = plexLocalUsers.FirstOrDefault(x => x.Username == username); + if (plexLocal != null) + { + loginGuid = Guid.Parse(plexLocal.LoginId); + } + } + if (emby) + { + var embyLocal = embyLocalUsers.FirstOrDefault(x => x.Username == username); + if (embyLocal != null) + { + loginGuid = Guid.Parse(embyLocal.LoginId); + } } var dbUser = localUsers.FirstOrDefault(x => x.UserName == username); if (dbUser != null) { loginGuid = Guid.Parse(dbUser.UserGuid); + localUser = true; } - //if (loginGuid != Guid.Empty) - //{ - // if (!settings.UserAuthentication)// Do not need to auth make admin use login screen for now TODO remove this - // { - // if (dbUser != null) - // { - // var perms = (Permissions)dbUser.Permissions; - // if (perms.HasFlag(Permissions.Administrator)) - // { - // var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); - // Session["TempMessage"] = Resources.UI.UserLogin_AdminUsePassword; - // //return Response.AsRedirect(uri.ToString()); - // } - // } - // if (plexLocal != null) - // { - // var perms = (Permissions)plexLocal.Permissions; - // if (perms.HasFlag(Permissions.Administrator)) - // { - // var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); - // Session["TempMessage"] = Resources.UI.UserLogin_AdminUsePassword; - // //return Response.AsRedirect(uri.ToString()); - // } - // } - // } - //} - if (loginGuid == Guid.Empty && settings.UserAuthentication) { var defaultSettings = UserManagementSettings.GetSettings(); @@ -620,21 +643,52 @@ namespace Ombi.UI.Modules defaultPermissions += (int)Permissions.Administrator; } } - - // Looks like we still don't have an entry, so this user does not exist - await PlexUserRepository.InsertAsync(new PlexUsers + if (plex) + { + // Looks like we still don't have an entry, so this user does not exist + await PlexUserRepository.InsertAsync(new PlexUsers + { + PlexUserId = userId, + UserAlias = string.Empty, + Permissions = (int) defaultPermissions, + Features = UserManagementHelper.GetPermissions(defaultSettings), + Username = username, + EmailAddress = string.Empty, + // We don't have it, we will get it on the next scheduled job run (in 30 mins) + LoginId = loginGuid.ToString() + }); + } + if (emby) { - PlexUserId = userId, - UserAlias = string.Empty, - Permissions = (int)defaultPermissions, - Features = UserManagementHelper.GetPermissions(defaultSettings), - Username = username, - EmailAddress = string.Empty, // We don't have it, we will get it on the next scheduled job run (in 30 mins) - LoginId = loginGuid.ToString() - }); + await EmbyUserRepository.InsertAsync(new EmbyUsers + { + EmbyUserId = userId, + UserAlias = string.Empty, + Permissions = (int)defaultPermissions, + Features = UserManagementHelper.GetPermissions(defaultSettings), + Username = username, + EmailAddress = string.Empty, + LoginId = loginGuid.ToString() + }); + } } m.LoginGuid = loginGuid; m.UserId = userId; + var type = UserType.LocalUser; + if (localUser) + { + type = UserType.LocalUser; + } + else if (plex) + { + type = UserType.PlexUser; + } + else if (emby) + { + type = UserType.EmbyUser;; + } + UserLogins.Insert(new UserLogins { UserId = userId, Type = type, LastLoggedIn = DateTime.UtcNow }); + return m; } @@ -676,6 +730,19 @@ namespace Ombi.UI.Modules return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase)); } + private bool CheckIfEmbyUser(string username, EmbySettings s) + { + var users = EmbyApi.GetUsers(s.FullUri, s.ApiKey); + var allUsers = users?.Where(x => !string.IsNullOrEmpty(x.Name)); + return allUsers != null && allUsers.Any(x => x.Name.Equals(username, StringComparison.CurrentCultureIgnoreCase)); + } + private EmbyUser GetEmbyUser(string username, EmbySettings s) + { + var users = EmbyApi.GetUsers(s.FullUri, s.ApiKey); + var allUsers = users?.Where(x => !string.IsNullOrEmpty(x.Name)); + return allUsers?.FirstOrDefault(x => x.Name.Equals(username, StringComparison.CurrentCultureIgnoreCase)); + } + private string GetUserIdIsInPlexFriends(string username, string authToken) { diff --git a/Ombi.UI/Modules/UserManagementModule.cs b/Ombi.UI/Modules/UserManagementModule.cs index af62f880e..d7ea20364 100644 --- a/Ombi.UI/Modules/UserManagementModule.cs +++ b/Ombi.UI/Modules/UserManagementModule.cs @@ -7,6 +7,7 @@ using Nancy.Extensions; using Nancy.Responses.Negotiation; using Newtonsoft.Json; using Ombi.Api.Interfaces; +using Ombi.Api.Models.Emby; using Ombi.Api.Models.Plex; using Ombi.Core; using Ombi.Core.Models; @@ -16,6 +17,8 @@ using Ombi.Helpers.Analytics; using Ombi.Helpers.Permissions; using Ombi.Store; using Ombi.Store.Models; +using Ombi.Store.Models.Emby; +using Ombi.Store.Models.Plex; using Ombi.Store.Repository; using Ombi.UI.Models; using Ombi.UI.Models.UserManagement; @@ -26,8 +29,8 @@ namespace Ombi.UI.Modules { public class UserManagementModule : BaseModule { - public UserManagementModule(ISettingsService pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService plex, IRepository userLogins, IPlexUserRepository plexRepo - , ISecurityExtensions security, IRequestService req, IAnalytics ana) : base("usermanagement", pr, security) + public UserManagementModule(ISettingsService pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService plex, IRepository userLogins, IExternalUserRepository plexRepo + , ISecurityExtensions security, IRequestService req, IAnalytics ana, ISettingsService embyService, IEmbyApi embyApi, IExternalUserRepository embyRepo) : base("usermanagement", pr, security) { #if !DEBUG Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); @@ -40,6 +43,9 @@ namespace Ombi.UI.Modules PlexRequestSettings = pr; RequestService = req; Analytics = ana; + EmbySettings = embyService; + EmbyApi = embyApi; + EmbyRepository = embyRepo; Get["/"] = x => Load(); @@ -57,10 +63,13 @@ namespace Ombi.UI.Modules private IPlexApi PlexApi { get; } private ISettingsService PlexSettings { get; } private IRepository UserLoginsRepo { get; } - private IPlexUserRepository PlexUsersRepository { get; } + private IExternalUserRepository PlexUsersRepository { get; } + private IExternalUserRepository EmbyRepository { get; } private ISettingsService PlexRequestSettings { get; } + private ISettingsService EmbySettings { get; } private IRequestService RequestService { get; } private IAnalytics Analytics { get; } + private IEmbyApi EmbyApi { get; } private Negotiator Load() { @@ -69,48 +78,19 @@ namespace Ombi.UI.Modules private async Task LoadUsers() { - var localUsers = await UserMapper.GetUsersAsync(); - var plexDbUsers = await PlexUsersRepository.GetAllAsync(); - var model = new List(); - - var userLogins = UserLoginsRepo.GetAll().ToList(); - foreach (var user in localUsers) + var plexSettings = await PlexSettings.GetSettingsAsync(); + var embySettings = await EmbySettings.GetSettingsAsync(); + if (plexSettings.Enable) { - var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid); - model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue)); + return await LoadPlexUsers(); } - - var plexSettings = await PlexSettings.GetSettingsAsync(); - if (!string.IsNullOrEmpty(plexSettings.PlexAuthToken)) + if (embySettings.Enable) { - //Get Plex Users - var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken); - if (plexUsers != null && plexUsers.User != null) { - foreach (var u in plexUsers.User) { - var dbUser = plexDbUsers.FirstOrDefault (x => x.PlexUserId == u.Id); - var userDb = userLogins.FirstOrDefault (x => x.UserId == u.Id); - - // We don't have the user in the database yet - if (dbUser == null) { - model.Add (MapPlexUser (u, null, userDb?.LastLoggedIn ?? DateTime.MinValue)); - } else { - // The Plex User is in the database - model.Add (MapPlexUser (u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue)); - } - } - } - - // Also get the server admin - var account = PlexApi.GetAccount(plexSettings.PlexAuthToken); - if (account != null) - { - var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == account.Id); - var userDb = userLogins.FirstOrDefault(x => x.UserId == account.Id); - model.Add(MapPlexAdmin(account, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue)); - } + return await LoadEmbyUsers(); } - return Response.AsJson(model); + + return null; } private async Task CreateUser() @@ -217,64 +197,95 @@ namespace Ombi.UI.Modules } var plexSettings = await PlexSettings.GetSettingsAsync(); - var plexDbUsers = await PlexUsersRepository.GetAllAsync(); - var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken); - var plexDbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == model.Id); - var plexUser = plexUsers.User.FirstOrDefault(x => x.Id == model.Id); - var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id); - if (plexDbUser != null && plexUser != null) + if (plexSettings.Enable) { - // We have a user in the DB for this Plex Account - plexDbUser.Permissions = permissionsValue; - plexDbUser.Features = featuresValue; + var plexDbUsers = await PlexUsersRepository.GetAllAsync(); + var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken); + var plexDbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == model.Id); + var plexUser = plexUsers.User.FirstOrDefault(x => x.Id == model.Id); + var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id); + if (plexDbUser != null && plexUser != null) + { + // We have a user in the DB for this Plex Account + plexDbUser.Permissions = permissionsValue; + plexDbUser.Features = featuresValue; - await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias); + await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias); - plexDbUser.UserAlias = model.Alias; - plexDbUser.EmailAddress = model.EmailAddress; + plexDbUser.UserAlias = model.Alias; + plexDbUser.EmailAddress = model.EmailAddress; - await PlexUsersRepository.UpdateAsync(plexDbUser); + await PlexUsersRepository.UpdateAsync(plexDbUser); - var retUser = MapPlexUser(plexUser, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue); - return Response.AsJson(retUser); - } + var retUser = MapPlexUser(plexUser, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } - // So it could actually be the admin - var account = PlexApi.GetAccount(plexSettings.PlexAuthToken); - if (plexDbUser != null && account != null) - { - // We have a user in the DB for this Plex Account - plexDbUser.Permissions = permissionsValue; - plexDbUser.Features = featuresValue; + // So it could actually be the admin + var account = PlexApi.GetAccount(plexSettings.PlexAuthToken); + if (plexDbUser != null && account != null) + { + // We have a user in the DB for this Plex Account + plexDbUser.Permissions = permissionsValue; + plexDbUser.Features = featuresValue; - await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias); + await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias); - plexDbUser.UserAlias = model.Alias; + plexDbUser.UserAlias = model.Alias; - await PlexUsersRepository.UpdateAsync(plexDbUser); + await PlexUsersRepository.UpdateAsync(plexDbUser); - var retUser = MapPlexAdmin(account, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue); - return Response.AsJson(retUser); + var retUser = MapPlexAdmin(account, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } + + // We have a Plex Account but he's not in the DB + if (plexUser != null) + { + var user = new PlexUsers + { + Permissions = permissionsValue, + Features = featuresValue, + UserAlias = model.Alias, + PlexUserId = plexUser.Id, + EmailAddress = plexUser.Email, + Username = plexUser.Title, + LoginId = Guid.NewGuid().ToString() + }; + + await PlexUsersRepository.InsertAsync(user); + + var retUser = MapPlexUser(plexUser, user, userLogin?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } } - // We have a Plex Account but he's not in the DB - if (plexUser != null) + var embySettings = await EmbySettings.GetSettingsAsync(); + if (embySettings.Enable) { - var user = new PlexUsers + var embyDbUsers = await EmbyRepository.GetAllAsync(); + var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey); + var selectedDbUser = embyDbUsers.FirstOrDefault(x => x.EmbyUserId == model.Id); + var embyUser = embyUsers.FirstOrDefault(x => x.Id == model.Id); + + var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id); + if (selectedDbUser != null && embyUser != null) { - Permissions = permissionsValue, - Features = featuresValue, - UserAlias = model.Alias, - PlexUserId = plexUser.Id, - EmailAddress = plexUser.Email, - Username = plexUser.Title, - LoginId = Guid.NewGuid().ToString() - }; + // We have a user in the DB for this Plex Account + selectedDbUser.Permissions = permissionsValue; + selectedDbUser.Features = featuresValue; - await PlexUsersRepository.InsertAsync(user); + await UpdateRequests(selectedDbUser.Username, selectedDbUser.UserAlias, model.Alias); + + selectedDbUser.UserAlias = model.Alias; + selectedDbUser.EmailAddress = model.EmailAddress; + + await EmbyRepository.UpdateAsync(selectedDbUser); + + var retUser = MapEmbyUser(embyUser, selectedDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } - var retUser = MapPlexUser(plexUser, user, userLogin?.LastLoggedIn ?? DateTime.MinValue); - return Response.AsJson(retUser); } return null; // We should never end up here. } @@ -416,7 +427,7 @@ namespace Ombi.UI.Modules var m = new UserManagementUsersViewModel { Id = plexInfo.Id, - PermissionsFormattedString = newUser ? "Processing..." :( permissions == 0 ? "None" : permissions.ToString()), + PermissionsFormattedString = newUser ? "Processing..." : (permissions == 0 ? "None" : permissions.ToString()), FeaturesFormattedString = newUser ? "Processing..." : features.ToString(), Username = plexInfo.Title, Type = UserType.PlexUser, @@ -436,6 +447,36 @@ namespace Ombi.UI.Modules return m; } + private UserManagementUsersViewModel MapEmbyUser(EmbyUser embyInfo, EmbyUsers dbUser, DateTime lastLoggedIn) + { + var newUser = false; + if (dbUser == null) + { + newUser = true; + dbUser = new EmbyUsers(); + } + var features = (Features)dbUser?.Features; + var permissions = (Permissions)dbUser?.Permissions; + + var m = new UserManagementUsersViewModel + { + Id = embyInfo.Id, + PermissionsFormattedString = newUser ? "Processing..." : (permissions == 0 ? "None" : permissions.ToString()), + FeaturesFormattedString = newUser ? "Processing..." : features.ToString(), + Username = embyInfo.Name, + Type = UserType.EmbyUser, + EmailAddress =dbUser.EmailAddress, + Alias = dbUser?.UserAlias ?? string.Empty, + LastLoggedIn = lastLoggedIn, + ManagedUser = false + }; + + m.Permissions.AddRange(GetPermissions(permissions)); + m.Features.AddRange(GetFeatures(features)); + + return m; + } + private UserManagementUsersViewModel MapPlexAdmin(PlexAccount plexInfo, PlexUsers dbUser, DateTime lastLoggedIn) { var newUser = false; @@ -505,6 +546,93 @@ namespace Ombi.UI.Modules } return retVal; } + + private async Task LoadPlexUsers() + { + var localUsers = await UserMapper.GetUsersAsync(); + var plexDbUsers = await PlexUsersRepository.GetAllAsync(); + var model = new List(); + + var userLogins = UserLoginsRepo.GetAll().ToList(); + + foreach (var user in localUsers) + { + var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid); + model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } + + var plexSettings = await PlexSettings.GetSettingsAsync(); + if (!string.IsNullOrEmpty(plexSettings.PlexAuthToken)) + { + //Get Plex Users + var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken); + if (plexUsers?.User != null) + { + foreach (var u in plexUsers.User) + { + var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == u.Id); + var userDb = userLogins.FirstOrDefault(x => x.UserId == u.Id); + + // We don't have the user in the database yet + if (dbUser == null) + { + model.Add(MapPlexUser(u, null, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } + else + { + // The Plex User is in the database + model.Add(MapPlexUser(u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } + } + } + + // Also get the server admin + var account = PlexApi.GetAccount(plexSettings.PlexAuthToken); + if (account != null) + { + var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == account.Id); + var userDb = userLogins.FirstOrDefault(x => x.UserId == account.Id); + model.Add(MapPlexAdmin(account, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } + } + return Response.AsJson(model); + } + + private async Task LoadEmbyUsers() + { + var localUsers = await UserMapper.GetUsersAsync(); + var embyDbUsers = await EmbyRepository.GetAllAsync(); + var model = new List(); + + var userLogins = UserLoginsRepo.GetAll().ToList(); + + foreach (var user in localUsers) + { + var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid); + model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } + + var embySettings = await EmbySettings.GetSettingsAsync(); + if (!string.IsNullOrEmpty(embySettings.ApiKey)) + { + //Get Plex Users + var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey); + if (embyUsers != null) + { + foreach (var u in embyUsers) + { + var dbUser = embyDbUsers.FirstOrDefault(x => x.EmbyUserId == u.Id); + var userDb = userLogins.FirstOrDefault(x => x.UserId == u.Id); + + // We don't have the user in the database yet + model.Add(dbUser == null + ? MapEmbyUser(u, null, userDb?.LastLoggedIn ?? DateTime.MinValue) + : MapEmbyUser(u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } + } + } + return Response.AsJson(model); + } } } diff --git a/Ombi.UI/Modules/UserWizardModule.cs b/Ombi.UI/Modules/UserWizardModule.cs index 922647a41..884d90e52 100644 --- a/Ombi.UI/Modules/UserWizardModule.cs +++ b/Ombi.UI/Modules/UserWizardModule.cs @@ -1,4 +1,5 @@ #region Copyright + // /************************************************************************ // Copyright (c) 2016 Jamie Rees // File: UserWizardModule.cs @@ -23,6 +24,7 @@ // 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; @@ -50,8 +52,11 @@ namespace Ombi.UI.Modules { public class UserWizardModule : BaseModule { - public UserWizardModule(ISettingsService pr, ISettingsService plex, IPlexApi plexApi, - ISettingsService auth, ICustomUserMapper m, IAnalytics a, ISecurityExtensions security) : base("wizard", pr, security) + public UserWizardModule(ISettingsService pr, ISettingsService plex, + IPlexApi plexApi, + ISettingsService auth, ICustomUserMapper m, IAnalytics a, + ISecurityExtensions security, IEmbyApi embyApi, + ISettingsService embySettings) : base("wizard", pr, security) { PlexSettings = plex; PlexApi = plexApi; @@ -59,10 +64,13 @@ namespace Ombi.UI.Modules Auth = auth; Mapper = m; Analytics = a; + EmbySettings = embySettings; + EmbyApi = embyApi; Get["/", true] = async (x, ct) => { - a.TrackEventAsync(Category.Wizard, Action.Start, "Started the wizard", Username, CookieHelper.GetAnalyticClientId(Cookies)); + a.TrackEventAsync(Category.Wizard, Action.Start, "Started the wizard", Username, + CookieHelper.GetAnalyticClientId(Cookies)); var settings = await PlexRequestSettings.GetSettingsAsync(); @@ -76,7 +84,10 @@ namespace Ombi.UI.Modules Post["/plex", true] = async (x, ct) => await Plex(); Post["/plexrequest", true] = async (x, ct) => await PlexRequest(); Post["/auth", true] = async (x, ct) => await Authentication(); - Post["/createuser",true] = async (x,ct) => await CreateUser(); + Post["/createuser", true] = async (x, ct) => await CreateUser(); + + + Post["/embyauth", true] = async (x, ct) => await EmbyAuth(); } private ISettingsService PlexSettings { get; } @@ -85,6 +96,8 @@ namespace Ombi.UI.Modules private ISettingsService Auth { get; } private ICustomUserMapper Mapper { get; } private IAnalytics Analytics { get; } + private IEmbyApi EmbyApi { get; } + private ISettingsService EmbySettings { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); @@ -95,23 +108,31 @@ namespace Ombi.UI.Modules if (string.IsNullOrEmpty(user.username) || string.IsNullOrEmpty(user.password)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Please provide a valid username and password" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Please provide a valid username and password" + }); } var model = PlexApi.SignIn(user.username, user.password); if (model?.user == null) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect username or password!" }); + return + Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect username or password!" }); } // Set the auth token in the session so we can use it in the next form Session[SessionKeys.UserWizardPlexAuth] = model.user.authentication_token; - + var servers = PlexApi.GetServer(model.user.authentication_token); var firstServer = servers.Server.FirstOrDefault(); - - return Response.AsJson(new { Result = true, firstServer?.Port, Ip = firstServer?.LocalAddresses, firstServer?.Scheme }); + + return + Response.AsJson( + new { Result = true, firstServer?.Port, Ip = firstServer?.LocalAddresses, firstServer?.Scheme }); } private async Task Plex() @@ -122,7 +143,8 @@ namespace Ombi.UI.Modules { return Response.AsJson(valid.SendJsonError()); } - form.PlexAuthToken = Session[SessionKeys.UserWizardPlexAuth].ToString(); // Set the auth token from the previous form + form.PlexAuthToken = Session[SessionKeys.UserWizardPlexAuth].ToString(); + // Set the auth token from the previous form // Get the machine ID from the settings (This could have changed) try @@ -131,6 +153,7 @@ namespace Ombi.UI.Modules var firstServer = servers.Server.FirstOrDefault(x => x.AccessToken == form.PlexAuthToken); Session[SessionKeys.UserWizardMachineId] = firstServer?.MachineIdentifier; + form.MachineIdentifier = firstServer?.MachineIdentifier; } catch (Exception e) { @@ -143,7 +166,12 @@ namespace Ombi.UI.Modules { return Response.AsJson(new JsonResponseModel { Result = true }); } - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save the settings to the database, please try again." }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Could not save the settings to the database, please try again." + }); } private async Task PlexRequest() @@ -158,14 +186,19 @@ namespace Ombi.UI.Modules currentSettings.SearchForMovies = form.SearchForMovies; currentSettings.SearchForTvShows = form.SearchForTvShows; currentSettings.SearchForMusic = form.SearchForMusic; - + var result = await PlexRequestSettings.SaveSettingsAsync(currentSettings); if (result) { return Response.AsJson(new { Result = true }); } - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save the settings to the database, please try again." }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Could not save the settings to the database, please try again." + }); } private async Task Authentication() @@ -177,14 +210,21 @@ namespace Ombi.UI.Modules { return Response.AsJson(new JsonResponseModel { Result = true }); } - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save the settings to the database, please try again." }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Could not save the settings to the database, please try again." + }); } private async Task CreateUser() { var username = (string)Request.Form.Username; - var userId = Mapper.CreateUser(username, Request.Form.Password, EnumHelper.All() - (int)Permissions.ReadOnlyUser, 0); - Analytics.TrackEventAsync(Category.Wizard, Action.Finish, "Finished the wizard", username, CookieHelper.GetAnalyticClientId(Cookies)); + var userId = Mapper.CreateUser(username, Request.Form.Password, + EnumHelper.All() - (int)Permissions.ReadOnlyUser, 0); + Analytics.TrackEventAsync(Category.Wizard, Action.Finish, "Finished the wizard", username, + CookieHelper.GetAnalyticClientId(Cookies)); Session[SessionKeys.UsernameKey] = username; // Destroy the Plex Auth Token @@ -197,7 +237,55 @@ namespace Ombi.UI.Modules var baseUrl = string.IsNullOrEmpty(settings.BaseUrl) ? string.Empty : $"/{settings.BaseUrl}"; - return CustomModuleExtensions.LoginAndRedirect(this,(Guid)userId, fallbackRedirectUrl: $"{baseUrl}/search"); + return CustomModuleExtensions.LoginAndRedirect(this, (Guid)userId, fallbackRedirectUrl: $"{baseUrl}/search"); + } + + private async Task EmbyAuth() + { + var ip = (string)Request.Form.Ip; + var port = (int)Request.Form.Port; + var apiKey = (string)Request.Form.ApiKey; + var ssl = (bool)Request.Form.Ssl; + + var settings = new EmbySettings + { + ApiKey = apiKey, + Enable = true, + Ip = ip, + Port = port, + Ssl = ssl, + }; + + try + { + // Test that we can connect + var result = EmbyApi.GetUsers(settings.FullUri, apiKey); + + if (result != null && result.Any()) + { + settings.AdministratorId = result.FirstOrDefault(x => x.Policy.IsAdministrator)?.Id ?? string.Empty; + await EmbySettings.SaveSettingsAsync(settings); + + return Response.AsJson(new JsonResponseModel + { + Result = true + }); + } + } + catch (Exception e) + { + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"Could not connect to Emby, please check your settings. Error: {e.Message}" + }); + } + + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "Could not connect to Emby, please check your settings." + }); } } } \ No newline at end of file diff --git a/Ombi.UI/NinjectModules/ApiModule.cs b/Ombi.UI/NinjectModules/ApiModule.cs index 1a45764c7..8ca7c5dbd 100644 --- a/Ombi.UI/NinjectModules/ApiModule.cs +++ b/Ombi.UI/NinjectModules/ApiModule.cs @@ -50,6 +50,7 @@ namespace Ombi.UI.NinjectModules Bind().To(); Bind().To(); Bind().To(); + Bind().To(); } } } \ No newline at end of file diff --git a/Ombi.UI/NinjectModules/ConfigurationModule.cs b/Ombi.UI/NinjectModules/ConfigurationModule.cs index 46937b65f..639194228 100644 --- a/Ombi.UI/NinjectModules/ConfigurationModule.cs +++ b/Ombi.UI/NinjectModules/ConfigurationModule.cs @@ -56,7 +56,8 @@ namespace Ombi.UI.NinjectModules Bind().To(); Bind().To().InSingletonScope(); - Bind().To(); + Bind().To(); + Bind().To(); Bind().To(); diff --git a/Ombi.UI/NinjectModules/RepositoryModule.cs b/Ombi.UI/NinjectModules/RepositoryModule.cs index e443924f1..601a7e89f 100644 --- a/Ombi.UI/NinjectModules/RepositoryModule.cs +++ b/Ombi.UI/NinjectModules/RepositoryModule.cs @@ -40,6 +40,7 @@ namespace Ombi.UI.NinjectModules { Bind>().To>(); Bind(typeof(IRepository<>)).To(typeof(GenericRepository<>)); + Bind(typeof(IExternalUserRepository<>)).To(typeof(BaseExternalUserRepository<>)); Bind().To(); Bind().To(); @@ -48,7 +49,6 @@ namespace Ombi.UI.NinjectModules Bind().To(); Bind().To(); - Bind().To(); } } diff --git a/Ombi.UI/NinjectModules/ServicesModule.cs b/Ombi.UI/NinjectModules/ServicesModule.cs index edafddb03..95dfcf433 100644 --- a/Ombi.UI/NinjectModules/ServicesModule.cs +++ b/Ombi.UI/NinjectModules/ServicesModule.cs @@ -31,6 +31,7 @@ using Ombi.Core.Queue; using Ombi.Helpers.Analytics; using Ombi.Services.Interfaces; using Ombi.Services.Jobs; +using Ombi.Services.Jobs.Interfaces; using Ombi.UI.Jobs; using Quartz; using Quartz.Impl; @@ -58,6 +59,12 @@ namespace Ombi.UI.NinjectModules Bind().To(); Bind().To(); Bind().To(); + + Bind().To(); + Bind().To(); + Bind().To(); + Bind().To(); + Bind().To(); Bind().To(); diff --git a/Ombi.UI/Ombi.UI.csproj b/Ombi.UI/Ombi.UI.csproj index 24fa57f52..eebd32612 100644 --- a/Ombi.UI/Ombi.UI.csproj +++ b/Ombi.UI/Ombi.UI.csproj @@ -295,6 +295,7 @@
+ @@ -392,6 +393,24 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -483,9 +502,6 @@ PreserveNewest - - PreserveNewest - Always @@ -803,6 +819,9 @@ Always + + Always + web.config diff --git a/Ombi.UI/Program.cs b/Ombi.UI/Program.cs index 4b563f7df..b454550c5 100644 --- a/Ombi.UI/Program.cs +++ b/Ombi.UI/Program.cs @@ -110,7 +110,7 @@ namespace Ombi.UI { Log.Info("This is not Mono"); Console.WriteLine("Press any key to exit"); - Console.ReadLine(); + Console.ReadLine(); } } } diff --git a/Ombi.UI/Startup.cs b/Ombi.UI/Startup.cs index ff8590da4..73ef53250 100644 --- a/Ombi.UI/Startup.cs +++ b/Ombi.UI/Startup.cs @@ -31,6 +31,7 @@ using Ninject; using Ninject.Planning.Bindings.Resolvers; using Ninject.Syntax; using NLog; +using Ombi.Api; using Ombi.Api.Interfaces; using Ombi.Core; using Ombi.Core.Migration; @@ -83,6 +84,7 @@ namespace Ombi.UI var scheduler = new Scheduler(); + // Reset any jobs running var jobSettings = kernel.Get>(); var all = jobSettings.GetAll(); diff --git a/Ombi.UI/Validators/EmbyValidator.cs b/Ombi.UI/Validators/EmbyValidator.cs new file mode 100644 index 000000000..84fed40dd --- /dev/null +++ b/Ombi.UI/Validators/EmbyValidator.cs @@ -0,0 +1,42 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrValidator.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 FluentValidation; +using Ombi.Core.SettingModels; + +namespace Ombi.UI.Validators +{ + public class EmbyValidator : AbstractValidator + { + public EmbyValidator() + { + RuleFor(request => request.Ip).NotNull().WithMessage("You must specify a IP/Host name."); + RuleFor(request => request.Port).NotNull().WithMessage("You must specify a Port."); + RuleFor(request => request.ApiKey).NotNull().WithMessage("You must specify a Api Key."); + } + } +} \ No newline at end of file diff --git a/Ombi.UI/Views/Admin/Emby.cshtml b/Ombi.UI/Views/Admin/Emby.cshtml new file mode 100644 index 000000000..f0b05c979 --- /dev/null +++ b/Ombi.UI/Views/Admin/Emby.cshtml @@ -0,0 +1,146 @@ +@using Ombi.UI.Helpers +@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase +@Html.Partial("Shared/Partial/_Sidebar") +@{ + int port; + if (Model.Port == 0) + { + port = 8096; + } + else + { + port = Model.Port; + } +} +
+
+
+ Emby Settings + + @Html.Checkbox(Model.Enable, "Enable", "Enabled") +
+ +
+ +
+
+ +
+ + +
+ +
+
+
+
+ + @if (Model.Ssl) + { + + } + else + { + + } + +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/Ombi.UI/Views/Admin/Plex.cshtml b/Ombi.UI/Views/Admin/Plex.cshtml index 4157d4bfb..50ee63e19 100644 --- a/Ombi.UI/Views/Admin/Plex.cshtml +++ b/Ombi.UI/Views/Admin/Plex.cshtml @@ -17,6 +17,8 @@
Plex Settings @**@ @*TODO*@ + + @Html.Checkbox(Model.Enable, "Enable", "Enable")
diff --git a/Ombi.UI/Views/Admin/SchedulerSettings.cshtml b/Ombi.UI/Views/Admin/SchedulerSettings.cshtml index d0ec15837..ac2e0f4b8 100644 --- a/Ombi.UI/Views/Admin/SchedulerSettings.cshtml +++ b/Ombi.UI/Views/Admin/SchedulerSettings.cshtml @@ -35,22 +35,53 @@
Scheduler Settings Please note, you will need to restart for these settings to take effect + + + @if (Model.Plex) + { +
+ + +
-
- - -
+
+ + +
-
- - -
- -
- - -
+
+ + +
+ Please note, the minimum time for this to run is 11 hours, if set below 11 then we will ignore that value. This is a very resource intensive job, the less we run it the better. +
+ + +
+ } + @if (Model.Emby) + { +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ }
@@ -64,11 +95,7 @@
- Please note, the minimum time for this to run is 11 hours, if set below 11 then we will ignore that value. This is a very resource intensive job, the less we run it the better. -
- - -
+
diff --git a/Ombi.UI/Views/Search/Index.cshtml b/Ombi.UI/Views/Search/Index.cshtml index db5b25dd9..cef2e73e8 100644 --- a/Ombi.UI/Views/Search/Index.cshtml +++ b/Ombi.UI/Views/Search/Index.cshtml @@ -161,7 +161,7 @@
{{#if available}} - @UI.Search_Available_on_plex + @UI.Search_Available {{else}} {{#if approved}} @UI.Search_Processing_Request @@ -207,7 +207,9 @@

+ {{#if url}} @UI.Search_ViewInPlex + {{/if}} {{else}} {{#if_eq requested true}} @@ -325,7 +327,7 @@ Release Date: {{releaseDate}} {{/if}} {{#if available}} - @UI.Search_Available_on_plex + @UI.Search_Available {{else}} {{#if approved}} @UI.Search_Processing_Request @@ -357,9 +359,11 @@ {{#if_eq type "movie"}} {{#if_eq available true}} + {{#if url}}

@UI.Search_ViewInPlex + {{/if}} {{else}} {{#if_eq requested true}} @@ -396,9 +400,11 @@
{{/if_eq}} {{#if available}} + {{#if url}}
@UI.Search_ViewInPlex {{/if}} + {{/if}} {{/if_eq}} {{/if_eq}} @@ -432,7 +438,7 @@ - + + +