diff options
36 files changed, 529 insertions, 166 deletions
diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 446415fbb..9c8120de7 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -9,7 +9,9 @@ using ServiceStack.Web; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.MediaEncoding; namespace MediaBrowser.Api { @@ -71,6 +73,14 @@ namespace MediaBrowser.Api } + [Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")] + [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] + public class UpdateMediaEncoderPath : IReturnVoid + { + [ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Path { get; set; } + } + public class ConfigurationService : BaseApiService { /// <summary> @@ -86,14 +96,22 @@ namespace MediaBrowser.Api private readonly IFileSystem _fileSystem; private readonly IProviderManager _providerManager; private readonly ILibraryManager _libraryManager; + private readonly IMediaEncoder _mediaEncoder; - public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager) + public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager, IMediaEncoder mediaEncoder) { _jsonSerializer = jsonSerializer; _configurationManager = configurationManager; _fileSystem = fileSystem; _providerManager = providerManager; _libraryManager = libraryManager; + _mediaEncoder = mediaEncoder; + } + + public void Post(UpdateMediaEncoderPath request) + { + var task = _mediaEncoder.UpdateEncoderPath(request.Path); + Task.WaitAll(task); } /// <summary> diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index 30cde4883..357ff4394 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/MediaBrowser.Api/ItemLookupService.cs @@ -38,6 +38,12 @@ namespace MediaBrowser.Api { } + [Route("/Items/RemoteSearch/Trailer", "POST")] + [Authenticated] + public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>> + { + } + [Route("/Items/RemoteSearch/AdultVideo", "POST")] [Authenticated] public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>> @@ -132,6 +138,13 @@ namespace MediaBrowser.Api return ToOptimizedResult(infos); } + public async Task<object> Post(GetTrailerRemoteSearchResults request) + { + var result = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(request, CancellationToken.None).ConfigureAwait(false); + + return ToOptimizedResult(result); + } + public async Task<object> Post(GetMovieRemoteSearchResults request) { var result = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).ConfigureAwait(false); @@ -181,11 +194,9 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } - public async Task<object> Get(GetRemoteSearchImage request) + public Task<object> Get(GetRemoteSearchImage request) { - var result = GetRemoteImage(request).ConfigureAwait(false); - - return result; + return GetRemoteImage(request); } public void Post(ApplySearchCriteria request) diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 93c9f8b9f..f993bc4b1 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -11,6 +11,7 @@ using ServiceStack; using System; using System.Linq; using System.Threading.Tasks; +using MediaBrowser.Controller.MediaEncoding; namespace MediaBrowser.Api { @@ -52,14 +53,16 @@ namespace MediaBrowser.Api private readonly IUserManager _userManager; private readonly IConnectManager _connectManager; private readonly ILiveTvManager _liveTvManager; + private readonly IMediaEncoder _mediaEncoder; - public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager) + public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager, IMediaEncoder mediaEncoder) { _config = config; _appHost = appHost; _userManager = userManager; _connectManager = connectManager; _liveTvManager = liveTvManager; + _mediaEncoder = mediaEncoder; } public void Post(ReportStartupWizardComplete request) @@ -75,7 +78,8 @@ namespace MediaBrowser.Api return new StartupInfo { - SupportsRunningAsService = info.SupportsRunningAsService + SupportsRunningAsService = info.SupportsRunningAsService, + HasMediaEncoder = !string.IsNullOrWhiteSpace(_mediaEncoder.EncoderPath) }; } @@ -114,7 +118,7 @@ namespace MediaBrowser.Api config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; config.EnableFolderView = true; - config.SchemaVersion = 95; + config.SchemaVersion = 96; } public void Post(UpdateStartupConfiguration request) @@ -231,6 +235,7 @@ namespace MediaBrowser.Api public class StartupInfo { public bool SupportsRunningAsService { get; set; } + public bool HasMediaEncoder { get; set; } } public class StartupUser diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs index 346f6b32a..c2318dccb 100644 --- a/MediaBrowser.Api/System/SystemService.cs +++ b/MediaBrowser.Api/System/SystemService.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Api.System /// Class GetSystemInfo /// </summary> [Route("/System/Info", "GET", Summary = "Gets information about the server")] - [Authenticated(EscapeParentalControl = true)] + [Authenticated(EscapeParentalControl = true, AllowBeforeStartupWizard = true)] public class GetSystemInfo : IReturn<SystemInfo> { @@ -120,7 +120,7 @@ namespace MediaBrowser.Api.System try { - files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath) + files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath) .Where(i => string.Equals(i.Extension, ".txt", StringComparison.OrdinalIgnoreCase)) .ToList(); } @@ -146,7 +146,7 @@ namespace MediaBrowser.Api.System public Task<object> Get(GetLogFile request) { - var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath) + var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath) .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase)); return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index f89cd0ef6..a7fc646a9 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -34,7 +34,6 @@ namespace MediaBrowser.Api.UserLibrary /// The _user manager /// </summary> private readonly IUserManager _userManager; - private readonly IUserDataManager _userDataRepository; /// <summary> /// The _library manager @@ -43,7 +42,6 @@ namespace MediaBrowser.Api.UserLibrary private readonly ILocalizationManager _localization; private readonly IDtoService _dtoService; - private readonly ICollectionManager _collectionManager; /// <summary> /// Initializes a new instance of the <see cref="ItemsService" /> class. @@ -58,10 +56,8 @@ namespace MediaBrowser.Api.UserLibrary { _userManager = userManager; _libraryManager = libraryManager; - _userDataRepository = userDataRepository; _localization = localization; _dtoService = dtoService; - _collectionManager = collectionManager; } /// <summary> @@ -119,11 +115,17 @@ namespace MediaBrowser.Api.UserLibrary // Default list type = children + var folder = item as Folder; + if (folder == null) + { + folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder(); + } + if (!string.IsNullOrEmpty(request.Ids)) { request.Recursive = true; var query = GetItemsQuery(request, user); - var result = await ((Folder)item).GetItems(query).ConfigureAwait(false); + var result = await folder.GetItems(query).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(request.SortBy)) { @@ -138,22 +140,22 @@ namespace MediaBrowser.Api.UserLibrary if (request.Recursive) { - return await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); + return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); } if (user == null) { - return await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false); + return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false); } var userRoot = item as UserRootFolder; if (userRoot == null) { - return await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); + return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); } - IEnumerable<BaseItem> items = ((Folder)item).GetChildren(user, true); + IEnumerable<BaseItem> items = folder.GetChildren(user, true); var itemsArray = items.ToArray(); diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index c00f76f22..77ba1685f 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -130,5 +130,7 @@ namespace MediaBrowser.Controller.MediaEncoding string EscapeSubtitleFilterPath(string path); void Init(); + + Task UpdateEncoderPath(string path); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 39a233856..f8321f6cd 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -23,6 +23,7 @@ using System.Threading.Tasks; using CommonIO; using MediaBrowser.Model.Configuration; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; namespace MediaBrowser.MediaEncoding.Encoder { @@ -118,6 +119,35 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + public async Task UpdateEncoderPath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException("path"); + } + + if (!File.Exists(path) && !Directory.Exists(path)) + { + throw new ResourceNotFoundException(); + } + + var newPaths = GetEncoderPaths(path); + if (string.IsNullOrWhiteSpace(newPaths.Item1)) + { + throw new ResourceNotFoundException("ffmpeg not found"); + } + if (string.IsNullOrWhiteSpace(newPaths.Item2)) + { + throw new ResourceNotFoundException("ffprobe not found"); + } + + var config = GetEncodingOptions(); + config.EncoderAppPath = path; + ConfigurationManager.SaveConfiguration("encoding", config); + + Init(); + } + private void ConfigureEncoderPaths() { if (_hasExternalEncoder) @@ -131,46 +161,60 @@ namespace MediaBrowser.MediaEncoding.Encoder { appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg"); } + var newPaths = GetEncoderPaths(appPath); + + if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2)) + { + FFMpegPath = newPaths.Item1; + FFProbePath = newPaths.Item2; + } + + LogPaths(); + } + + private Tuple<string, string> GetEncoderPaths(string configuredPath) + { + var appPath = configuredPath; if (!string.IsNullOrWhiteSpace(appPath)) { if (Directory.Exists(appPath)) { - SetPathsFromDirectory(appPath); + return GetPathsFromDirectory(appPath); } - else if (File.Exists(appPath)) + if (File.Exists(appPath)) { - FFMpegPath = appPath; - - SetProbePathFromEncoderPath(appPath); + return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath)); } } - LogPaths(); + return new Tuple<string, string>(null, null); } - private void SetPathsFromDirectory(string path) + private Tuple<string,string> GetPathsFromDirectory(string path) { // Since we can't predict the file extension, first try directly within the folder // If that doesn't pan out, then do a recursive search var files = Directory.GetFiles(path); - FFMpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase)); - FFProbePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); + var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase)); + var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); - if (string.IsNullOrWhiteSpace(FFMpegPath) || !File.Exists(FFMpegPath)) + if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath)) { files = Directory.GetFiles(path, "*", SearchOption.AllDirectories); - FFMpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase)); - SetProbePathFromEncoderPath(FFMpegPath); + ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase)); + ffprobePath = GetProbePathFromEncoderPath(ffmpegPath); } + + return new Tuple<string, string>(ffmpegPath, ffprobePath); } - private void SetProbePathFromEncoderPath(string appPath) + private string GetProbePathFromEncoderPath(string appPath) { - FFProbePath = Directory.GetFiles(Path.GetDirectoryName(appPath)) + return Directory.GetFiles(Path.GetDirectoryName(appPath)) .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); } diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index f9d28605e..0de9fb519 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -1136,6 +1136,9 @@ <Compile Include="..\MediaBrowser.Model\Sync\SyncTarget.cs"> <Link>Sync\SyncTarget.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\System\Architecture.cs"> + <Link>System\Architecture.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\System\LogFile.cs"> <Link>System\LogFile.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index edaa0e027..fe0b3bcae 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -1101,6 +1101,9 @@ <Compile Include="..\MediaBrowser.Model\Sync\SyncTarget.cs"> <Link>Sync\SyncTarget.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\System\Architecture.cs"> + <Link>System\Architecture.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\System\LogFile.cs"> <Link>System\LogFile.cs</Link> </Compile> diff --git a/MediaBrowser.Model/Channels/ChannelFolderType.cs b/MediaBrowser.Model/Channels/ChannelFolderType.cs index c456af69a..7c97afd02 100644 --- a/MediaBrowser.Model/Channels/ChannelFolderType.cs +++ b/MediaBrowser.Model/Channels/ChannelFolderType.cs @@ -8,6 +8,10 @@ namespace MediaBrowser.Model.Channels PhotoAlbum = 2, - MusicArtist = 3 + MusicArtist = 3, + + Series = 4, + + Season = 5 } }
\ No newline at end of file diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 7c9f132db..e54273b84 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -395,6 +395,7 @@ <Compile Include="Sync\SyncProfileOption.cs" /> <Compile Include="Sync\SyncQualityOption.cs" /> <Compile Include="Sync\SyncTarget.cs" /> + <Compile Include="System\Architecture.cs" /> <Compile Include="System\LogFile.cs" /> <Compile Include="System\PublicSystemInfo.cs" /> <Compile Include="Updates\CheckForUpdateResult.cs" /> diff --git a/MediaBrowser.Model/System/Architecture.cs b/MediaBrowser.Model/System/Architecture.cs new file mode 100644 index 000000000..09eedddc1 --- /dev/null +++ b/MediaBrowser.Model/System/Architecture.cs @@ -0,0 +1,9 @@ +namespace MediaBrowser.Model.System +{ + public enum Architecture + { + X86 = 0, + X64 = 1, + Arm = 2 + } +} diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 73d5961f6..868d9dc28 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -154,6 +154,8 @@ namespace MediaBrowser.Model.System public bool HasExternalEncoder { get; set; } + public Architecture SystemArchitecture { get; set; } + /// <summary> /// Initializes a new instance of the <see cref="SystemInfo" /> class. /// </summary> diff --git a/MediaBrowser.Providers/Movies/MovieDbSearch.cs b/MediaBrowser.Providers/Movies/MovieDbSearch.cs index ceb41178e..ab2cd3bed 100644 --- a/MediaBrowser.Providers/Movies/MovieDbSearch.cs +++ b/MediaBrowser.Providers/Movies/MovieDbSearch.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Movies internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; internal static string AcceptHeader = "application/json,image/*"; - + private readonly ILogger _logger; private readonly IJsonSerializer _json; private readonly ILibraryManager _libraryManager; @@ -54,6 +54,11 @@ namespace MediaBrowser.Providers.Movies var name = idInfo.Name; var year = idInfo.Year; + if (string.IsNullOrWhiteSpace(name)) + { + return new List<RemoteSearchResult>(); + } + var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original"; @@ -73,7 +78,7 @@ namespace MediaBrowser.Providers.Movies //var searchType = item is BoxSet ? "collection" : "movie"; var results = await GetSearchResults(name, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false); - + if (results.Count == 0) { //try in english if wasn't before @@ -123,19 +128,23 @@ namespace MediaBrowser.Providers.Movies }); } - private async Task<List<RemoteSearchResult>> GetSearchResults(string name, string type, int? year, string language, string baseImageUrl, CancellationToken cancellationToken) + private Task<List<RemoteSearchResult>> GetSearchResults(string name, string type, int? year, string language, string baseImageUrl, CancellationToken cancellationToken) { switch (type) { case "tv": - return await GetSearchResultsTv(name, year, language, baseImageUrl, cancellationToken); + return GetSearchResultsTv(name, year, language, baseImageUrl, cancellationToken); default: - return await GetSearchResultsGeneric(name, type, year, language, baseImageUrl, cancellationToken); + return GetSearchResultsGeneric(name, type, year, language, baseImageUrl, cancellationToken); } } private async Task<List<RemoteSearchResult>> GetSearchResultsGeneric(string name, string type, int? year, string language, string baseImageUrl, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("name"); + } var url3 = string.Format(Search3, WebUtility.UrlEncode(name), ApiKey, language, type); @@ -189,6 +198,11 @@ namespace MediaBrowser.Providers.Movies private async Task<List<RemoteSearchResult>> GetSearchResultsTv(string name, int? year, string language, string baseImageUrl, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("name"); + } + var url3 = string.Format(Search3, WebUtility.UrlEncode(name), ApiKey, language, "tv"); using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index b47e8df5a..a14c7123a 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using CommonIO; @@ -151,6 +152,7 @@ namespace MediaBrowser.Providers.Music } } + private Regex _regex_http = new Regex("^http://"); private void PopulateImages(List<RemoteImageInfo> list, List<FanartArtistProvider.FanartArtistImage> images, ImageType type, @@ -178,7 +180,7 @@ namespace MediaBrowser.Providers.Music Width = width, Height = height, ProviderName = Name, - Url = url, + Url = _regex_http.Replace(url, "https://", 1), Language = i.lang }; diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 9b18ac5e9..6afa80507 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -14,6 +14,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using CommonIO; @@ -149,6 +150,7 @@ namespace MediaBrowser.Providers.Music PopulateImages(list, obj.musicarts, ImageType.Art, 500, 281); } + private Regex _regex_http = new Regex("^http://"); private void PopulateImages(List<RemoteImageInfo> list, List<FanartArtistImage> images, ImageType type, @@ -176,7 +178,7 @@ namespace MediaBrowser.Providers.Music Width = width, Height = height, ProviderName = Name, - Url = url, + Url = _regex_http.Replace(url, "https://", 1), Language = i.lang }; diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 36676c6b2..1710ec2b0 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -339,7 +339,14 @@ namespace MediaBrowser.Providers.Music { var urls = await RefreshMzbUrls().ConfigureAwait(false); - _chosenUrl = urls[new Random().Next(0, urls.Count - 1)]; + if (urls.Count > 1) + { + _chosenUrl = urls[new Random().Next(0, urls.Count)]; + } + else + { + _chosenUrl = urls[0]; + } } return _chosenUrl; @@ -361,6 +368,7 @@ namespace MediaBrowser.Providers.Music { list = _json.DeserializeFromStream<List<MbzUrl>>(stream); } + _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks; } catch (Exception ex) { diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index f36062ebe..88635bf06 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -93,7 +93,7 @@ namespace MediaBrowser.Providers.Music { if (string.Equals(child.Name, "name", StringComparison.OrdinalIgnoreCase)) { - name = node.InnerText; + name = child.InnerText; break; } } diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs index 4056073f2..397107c74 100644 --- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Serialization; using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -114,6 +115,106 @@ namespace MediaBrowser.Providers.Omdb ParseAdditionalMetadata(item, result); } + public async Task<bool> FetchEpisodeData(BaseItem item, int episodeNumber, int seasonNumber, string imdbId, string language, string country, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(imdbId)) + { + throw new ArgumentNullException("imdbId"); + } + + var seasonResult = await GetSeasonRootObject(imdbId, seasonNumber, cancellationToken); + + RootObject result = null; + + foreach (var episode in seasonResult.Episodes) + { + if (episode.Episode == episodeNumber) + { + result = episode; + break; + } + } + + if (result == null) + { + return false; + } + + + // Only take the name and rating if the user's language is set to english, since Omdb has no localization + if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) + { + item.Name = result.Title; + + if (string.Equals(country, "us", StringComparison.OrdinalIgnoreCase)) + { + item.OfficialRating = result.Rated; + } + } + + int year; + + if (!string.IsNullOrEmpty(result.Year) + && int.TryParse(result.Year, NumberStyles.Number, _usCulture, out year) + && year >= 0) + { + item.ProductionYear = year; + } + + var hasCriticRating = item as IHasCriticRating; + if (hasCriticRating != null) + { + // Seeing some bogus RT data on omdb for series, so filter it out here + // RT doesn't even have tv series + int tomatoMeter; + + if (!string.IsNullOrEmpty(result.tomatoMeter) + && int.TryParse(result.tomatoMeter, NumberStyles.Integer, _usCulture, out tomatoMeter) + && tomatoMeter >= 0) + { + hasCriticRating.CriticRating = tomatoMeter; + } + + if (!string.IsNullOrEmpty(result.tomatoConsensus) + && !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase)) + { + hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus); + } + } + + int voteCount; + + if (!string.IsNullOrEmpty(result.imdbVotes) + && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out voteCount) + && voteCount >= 0) + { + item.VoteCount = voteCount; + } + + float imdbRating; + + if (!string.IsNullOrEmpty(result.imdbRating) + && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out imdbRating) + && imdbRating >= 0) + { + item.CommunityRating = imdbRating; + } + + if (!string.IsNullOrEmpty(result.Website)) + { + item.HomePageUrl = result.Website; + } + + if (!string.IsNullOrWhiteSpace(result.imdbID)) + { + item.SetProviderId(MetadataProviders.Imdb, result.imdbID); + } + + ParseAdditionalMetadata(item, result); + + return true; + } + internal async Task<RootObject> GetRootObject(string imdbId, CancellationToken cancellationToken) { var path = await EnsureItemInfo(imdbId, cancellationToken); @@ -133,6 +234,40 @@ namespace MediaBrowser.Providers.Omdb return result; } + internal async Task<SeasonRootObject> GetSeasonRootObject(string imdbId, int seasonId, CancellationToken cancellationToken) + { + var path = await EnsureSeasonInfo(imdbId, seasonId, cancellationToken); + + string resultString; + + using (Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072)) + { + using (var reader = new StreamReader(stream, new UTF8Encoding(false))) + { + resultString = reader.ReadToEnd(); + resultString = resultString.Replace("\"N/A\"", "\"\""); + } + } + + var result = _jsonSerializer.DeserializeFromString<SeasonRootObject>(resultString); + return result; + } + + internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds) + { + string id; + if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id) && !string.IsNullOrEmpty(id)) + { + // This check should ideally never be necessary but we're seeing some cases of this and haven't tracked them down yet. + if (!string.IsNullOrWhiteSpace(id)) + { + return true; + } + } + + return false; + } + private async Task<string> EnsureItemInfo(string imdbId, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(imdbId)) @@ -173,6 +308,46 @@ namespace MediaBrowser.Providers.Omdb return path; } + private async Task<string> EnsureSeasonInfo(string seriesImdbId, int seasonId, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(seriesImdbId)) + { + throw new ArgumentNullException("imdbId"); + } + + var imdbParam = seriesImdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? seriesImdbId : "tt" + seriesImdbId; + + var path = GetSeasonFilePath(imdbParam, seasonId); + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + + if (fileInfo.Exists) + { + // If it's recent or automatic updates are enabled, don't re-download + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3) + { + return path; + } + } + + var url = string.Format("https://www.omdbapi.com/?i={0}&season={1}&detail=full", imdbParam, seasonId); + + using (var stream = await _httpClient.Get(new HttpRequestOptions + { + Url = url, + ResourcePool = ResourcePool, + CancellationToken = cancellationToken + + }).ConfigureAwait(false)) + { + var rootObject = _jsonSerializer.DeserializeFromStream<SeasonRootObject>(stream); + _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + _jsonSerializer.SerializeToFile(rootObject, path); + } + + return path; + } + internal string GetDataFilePath(string imdbId) { if (string.IsNullOrEmpty(imdbId)) @@ -187,6 +362,20 @@ namespace MediaBrowser.Providers.Omdb return Path.Combine(dataPath, filename); } + internal string GetSeasonFilePath(string imdbId, int seasonId) + { + if (string.IsNullOrEmpty(imdbId)) + { + throw new ArgumentNullException("imdbId"); + } + + var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb"); + + var filename = string.Format("{0}_season_{1}.json", imdbId, seasonId); + + return Path.Combine(dataPath, filename); + } + private void ParseAdditionalMetadata(BaseItem item, RootObject result) { // Grab series genres because imdb data is better than tvdb. Leave movies alone @@ -238,12 +427,23 @@ namespace MediaBrowser.Providers.Omdb return string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase); } + internal class SeasonRootObject + { + public string Title { get; set; } + public string seriesID { get; set; } + public int Season { get; set; } + public int? totalSeasons { get; set; } + public RootObject[] Episodes { get; set; } + public string Response { get; set; } + } + internal class RootObject { public string Title { get; set; } public string Year { get; set; } public string Rated { get; set; } public string Released { get; set; } + public int Episode { get; set; } public string Runtime { get; set; } public string Genre { get; set; } public string Director { get; set; } diff --git a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs index b1930d8f0..e6a47d9d2 100644 --- a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using CommonIO; @@ -160,6 +161,7 @@ namespace MediaBrowser.Providers.TV PopulateImages(list, obj.showbackground, ImageType.Backdrop, 1920, 1080, seasonNumber); } + private Regex _regex_http = new Regex("^http://"); private void PopulateImages(List<RemoteImageInfo> list, List<FanartSeriesProvider.Image> images, ImageType type, @@ -194,7 +196,7 @@ namespace MediaBrowser.Providers.TV Width = width, Height = height, ProviderName = Name, - Url = url, + Url = _regex_http.Replace(url, "https://", 1), Language = i.lang }; diff --git a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs index 5662082db..827a3c50f 100644 --- a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs @@ -17,6 +17,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using CommonIO; @@ -162,6 +163,7 @@ namespace MediaBrowser.Providers.TV PopulateImages(list, obj.tvposter, ImageType.Primary, 1000, 1426); } + private Regex _regex_http = new Regex("^http://"); private void PopulateImages(List<RemoteImageInfo> list, List<Image> images, ImageType type, @@ -194,7 +196,7 @@ namespace MediaBrowser.Providers.TV Width = width, Height = height, ProviderName = Name, - Url = url, + Url = _regex_http.Replace(url, "https://", 1), Language = i.lang }; diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs index ebbefbeb1..21668f014 100644 --- a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) { - var result = new MetadataResult<Episode> + var result = new MetadataResult<Episode>() { Item = new Episode() }; @@ -53,30 +53,16 @@ namespace MediaBrowser.Providers.TV return result; } - var imdbId = info.GetProviderId(MetadataProviders.Imdb); - if (string.IsNullOrWhiteSpace(imdbId)) + if (OmdbProvider.IsValidSeries(info.SeriesProviderIds) && info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue) { - imdbId = await GetEpisodeImdbId(info, cancellationToken).ConfigureAwait(false); - } - - if (!string.IsNullOrEmpty(imdbId)) - { - result.Item.SetProviderId(MetadataProviders.Imdb, imdbId); - result.HasMetadata = true; + var seriesImdbId = info.SeriesProviderIds[MetadataProviders.Imdb.ToString()]; - await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager).Fetch(result.Item, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager).FetchEpisodeData(result.Item, info.IndexNumber.Value, info.ParentIndexNumber.Value, seriesImdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); } return result; } - private async Task<string> GetEpisodeImdbId(EpisodeInfo info, CancellationToken cancellationToken) - { - var results = await GetSearchResults(info, cancellationToken).ConfigureAwait(false); - var first = results.FirstOrDefault(); - return first == null ? null : first.GetProviderId(MetadataProviders.Imdb); - } - public int Order { get diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index 41b4eb82c..8efc600e9 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -1172,8 +1172,7 @@ namespace MediaBrowser.Server.Implementations.Channels { items = ApplyFilters(items, query.Filters, user); - var sortBy = query.SortBy.Length == 0 ? new[] { ItemSortBy.SortName } : query.SortBy; - items = _libraryManager.Sort(items, user, sortBy, query.SortOrder ?? SortOrder.Ascending); + items = _libraryManager.Sort(items, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending); var all = items.ToList(); var totalCount = totalCountFromProvider ?? all.Count; @@ -1258,6 +1257,14 @@ namespace MediaBrowser.Server.Implementations.Channels { item = GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); } + else if (info.FolderType == ChannelFolderType.Series) + { + item = GetItemById<Series>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); + } + else if (info.FolderType == ChannelFolderType.Season) + { + item = GetItemById<Season>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); + } else { item = GetItemById<Folder>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 357f5c976..bc3e7b163 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -104,6 +104,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security { info.DeviceId = tokenInfo.DeviceId; } + if (string.IsNullOrWhiteSpace(info.Version)) + { + info.Version = tokenInfo.AppVersion; + } } else { diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 29c1d1c9a..2d2d18524 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -461,7 +461,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return CreateTimer(info, cancellationToken); } - public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) + public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) { return CreateSeriesTimer(info, cancellationToken); } @@ -1011,7 +1011,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV Action onStarted = () => { timer.Status = RecordingStatus.InProgress; - _timerProvider.AddOrUpdate(timer); + _timerProvider.AddOrUpdate(timer, false); result.Item3.Release(); isResourceOpen = false; @@ -1060,7 +1060,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (recordingStatus == RecordingStatus.Completed) { timer.Status = RecordingStatus.Completed; - _timerProvider.AddOrUpdate(timer); + _timerProvider.AddOrUpdate(timer, false); OnSuccessfulRecording(info.IsSeries, recordPath); _timerProvider.Delete(timer); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index bcad80447..423358906 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -72,6 +72,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } + public void AddOrUpdate(TimerInfo item, bool resetTimer) + { + if (resetTimer) + { + AddOrUpdate(item); + return; + } + + var list = GetAll().ToList(); + + if (!list.Any(i => EqualityComparer(i, item))) + { + base.Add(item); + } + else + { + base.Update(item); + } + } + public override void Add(TimerInfo item) { if (string.IsNullOrWhiteSpace(item.Id)) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 992a0a2cf..6d067e345 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Persistence private IDbCommand _updateInheritedRatingCommand; private IDbCommand _updateInheritedTagsCommand; - public const int LatestSchemaVersion = 95; + public const int LatestSchemaVersion = 96; /// <summary> /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class. @@ -127,7 +127,9 @@ namespace MediaBrowser.Server.Implementations.Persistence connection.RunQueries(new[] { - "pragma temp_store = memory" + "pragma temp_store = memory", + "pragma default_temp_store = memory", + "PRAGMA locking_mode=EXCLUSIVE" }, Logger); @@ -138,7 +140,7 @@ namespace MediaBrowser.Server.Implementations.Persistence /// Opens the connection to the database /// </summary> /// <returns>Task.</returns> - public async Task Initialize() + public async Task Initialize(SqliteUserDataRepository userDataRepo) { _connection = await CreateConnection(false).ConfigureAwait(false); @@ -162,18 +164,22 @@ namespace MediaBrowser.Server.Implementations.Persistence "create index if not exists idx_ItemValues2 on ItemValues(ItemId,Type)", "create table if not exists ProviderIds (ItemId GUID, Name TEXT, Value TEXT, PRIMARY KEY (ItemId, Name))", - "create index if not exists Idx_ProviderIds on ProviderIds(ItemId)", + // covering index + "create index if not exists Idx_ProviderIds1 on ProviderIds(ItemId,Name,Value)", "create table if not exists Images (ItemId GUID NOT NULL, Path TEXT NOT NULL, ImageType INT NOT NULL, DateModified DATETIME, IsPlaceHolder BIT NOT NULL, SortOrder INT)", "create index if not exists idx_Images on Images(ItemId)", "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)", + + "drop index if exists idxPeopleItemId", "create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)", "create index if not exists idxPeopleName on People(Name)", "create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))", createMediaStreamsTableCommand, + "create index if not exists idx_mediastreams1 on mediastreams(ItemId)", }; @@ -260,29 +266,45 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.AddColumn(Logger, "TypedBaseItems", "Album", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT"); _connection.AddColumn(Logger, "TypedBaseItems", "SeriesName", "Text"); + _connection.AddColumn(Logger, "TypedBaseItems", "UserDataKey", "Text"); _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT"); _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text"); string[] postQueries = + { + // obsolete + "drop index if exists idx_TypedBaseItems", + "drop index if exists idx_mediastreams", + "drop index if exists idx_"+ChaptersTableName, + "drop index if exists idx_UserDataKeys1", + "drop index if exists idx_UserDataKeys2", + "drop index if exists idx_TypeTopParentId3", + "drop index if exists idx_TypeTopParentId2", + "drop index if exists idx_TypeTopParentId4", + "drop index if exists idx_Type", + "drop index if exists idx_TypeTopParentId", + "drop index if exists idx_GuidType", + "drop index if exists idx_TopParentId", + "drop index if exists idx_TypeTopParentId6", + "drop index if exists idx_ItemValues2", + "drop index if exists Idx_ProviderIds", + "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", - "create index if not exists idx_GuidType on TypedBaseItems(Guid,Type)", + "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)", + //"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)", "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)", - "create index if not exists idx_Type on TypedBaseItems(Type)", - "create index if not exists idx_TopParentId on TypedBaseItems(TopParentId)", - "create index if not exists idx_TypeTopParentId on TypedBaseItems(Type,TopParentId)", + + // covering index + "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)", // live tv programs "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)", // used by movie suggestions "create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)", - "create index if not exists idx_TypeTopParentId2 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem)", - "create index if not exists idx_TypeTopParentId3 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem)", - "create index if not exists idx_TypeTopParentId4 on TypedBaseItems(TopParentId,Type,IsVirtualItem)", "create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)", - "create index if not exists idx_TypeTopParentId6 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey)", // latest items "create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)", @@ -293,9 +315,10 @@ namespace MediaBrowser.Server.Implementations.Persistence // items by name "create index if not exists idx_ItemValues3 on ItemValues(ItemId,Type,CleanValue)", + "create index if not exists idx_ItemValues4 on ItemValues(ItemId,Type,Value,CleanValue)", - //"create index if not exists idx_UserDataKeys1 on UserDataKeys(ItemId)", - "create index if not exists idx_UserDataKeys2 on UserDataKeys(ItemId,Priority)" + // covering index + "create index if not exists idx_UserDataKeys3 on UserDataKeys(ItemId,Priority,UserDataKey)" }; _connection.RunQueries(postQueries, Logger); @@ -305,6 +328,8 @@ namespace MediaBrowser.Server.Implementations.Persistence new MediaStreamColumns(_connection, Logger).AddColumns(); DataExtensions.Attach(_connection, Path.Combine(_config.ApplicationPaths.DataPath, "userdata_v2.db"), "UserDataDb"); + await userDataRepo.Initialize(_connection).ConfigureAwait(false); + //await Vacuum(_connection).ConfigureAwait(false); } private readonly string[] _retriveItemColumns = @@ -486,7 +511,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "DateLastMediaAdded", "Album", "IsVirtualItem", - "SeriesName" + "SeriesName", + "UserDataKey" }; _saveItemCommand = _connection.CreateCommand(); _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -915,6 +941,8 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveItemCommand.GetParameter(index++).Value = null; } + _saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault(); + _saveItemCommand.Transaction = transaction; _saveItemCommand.ExecuteNonQuery(); @@ -1713,6 +1741,11 @@ namespace MediaBrowser.Server.Implementations.Persistence return string.Empty; } + if (_config.Configuration.SchemaVersion >= 96) + { + return " left join UserDataDb.UserData on UserDataKey=UserDataDb.UserData.Key And (UserId=@UserId)"; + } + return " left join UserDataDb.UserData on (select UserDataKey from UserDataKeys where ItemId=Guid order by Priority LIMIT 1)=UserDataDb.UserData.Key And (UserId=@UserId)"; } @@ -1818,7 +1851,7 @@ namespace MediaBrowser.Server.Implementations.Persistence var slowThreshold = 1000; #if DEBUG - slowThreshold = 80; + slowThreshold = 50; #endif if (elapsed >= slowThreshold) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs index ced43a3e3..812e0aa48 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs @@ -56,17 +56,23 @@ namespace MediaBrowser.Server.Implementations.Persistence /// Opens the connection to the database /// </summary> /// <returns>Task.</returns> - public async Task Initialize() + public async Task Initialize(IDbConnection connection) { - _connection = await CreateConnection(false).ConfigureAwait(false); + _connection = connection; string[] queries = { - "create table if not exists userdata (key nvarchar, userId GUID, rating float null, played bit, playCount int, isFavorite bit, playbackPositionTicks bigint, lastPlayedDate datetime null)", + "create table if not exists UserDataDb.userdata (key nvarchar, userId GUID, rating float null, played bit, playCount int, isFavorite bit, playbackPositionTicks bigint, lastPlayedDate datetime null)", - "create index if not exists idx_userdata on userdata(key)", - "create unique index if not exists userdataindex on userdata (key, userId)", - "create index if not exists userdataindex1 on userdata (key, userId)", + "drop index if exists UserDataDb.idx_userdata", + "drop index if exists UserDataDb.idx_userdata1", + "drop index if exists UserDataDb.idx_userdata2", + "drop index if exists UserDataDb.userdataindex1", + + "create unique index if not exists UserDataDb.userdataindex on userdata (key, userId)", + "create index if not exists UserDataDb.userdataindex2 on userdata (key, userId, played)", + "create index if not exists UserDataDb.userdataindex3 on userdata (key, userId, playbackPositionTicks)", + "create index if not exists UserDataDb.userdataindex4 on userdata (key, userId, isFavorite)", //pragmas "pragma temp_store = memory", @@ -332,18 +338,19 @@ namespace MediaBrowser.Server.Implementations.Persistence using (var cmd = _connection.CreateCommand()) { var index = 0; - var excludeIds = new List<string>(); + var userdataKeys = new List<string>(); var builder = new StringBuilder(); foreach (var key in keys) { var paramName = "@Key" + index; - excludeIds.Add("Key =" + paramName); + userdataKeys.Add("Key =" + paramName); cmd.Parameters.Add(cmd, paramName, DbType.String).Value = key; builder.Append(" WHEN Key=" + paramName + " THEN " + index); index++; + break; } - var keyText = string.Join(" OR ", excludeIds.ToArray()); + var keyText = string.Join(" OR ", userdataKeys.ToArray()); cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where userId=@userId AND (" + keyText + ") "; diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs index a012a19a3..5d7274356 100644 --- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; using MediaBrowser.Controller.Power; +using MediaBrowser.Model.System; using MediaBrowser.Server.Implementations.Persistence; using MediaBrowser.Server.Startup.Common.FFMpeg; using OperatingSystem = MediaBrowser.Server.Startup.Common.OperatingSystem; @@ -176,7 +177,7 @@ namespace MediaBrowser.Server.Mono.Native } else if (string.Equals(uname.machine, "x86_64", StringComparison.OrdinalIgnoreCase)) { - info.SystemArchitecture = Architecture.X86_X64; + info.SystemArchitecture = Architecture.X64; } else if (uname.machine.StartsWith("arm", StringComparison.OrdinalIgnoreCase)) { @@ -260,7 +261,7 @@ namespace MediaBrowser.Server.Mono.Native switch (environment.SystemArchitecture) { - case Architecture.X86_X64: + case Architecture.X64: info.Version = "20160124"; break; case Architecture.X86: @@ -283,7 +284,7 @@ namespace MediaBrowser.Server.Mono.Native switch (environment.SystemArchitecture) { - case Architecture.X86_X64: + case Architecture.X64: return new[] { "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x64-2.8.5.7z" @@ -300,7 +301,7 @@ namespace MediaBrowser.Server.Mono.Native switch (environment.SystemArchitecture) { - case Architecture.X86_X64: + case Architecture.X64: return new[] { "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z" diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index e7e6a9dc3..ec1ccbff5 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -587,8 +587,11 @@ namespace MediaBrowser.Server.Startup.Common RegisterSingleInstance(SubtitleEncoder); await displayPreferencesRepo.Initialize().ConfigureAwait(false); - await ConfigureUserDataRepositories().ConfigureAwait(false); - await itemRepo.Initialize().ConfigureAwait(false); + + var userDataRepo = new SqliteUserDataRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector()); + + ((UserDataManager)UserDataManager).Repository = userDataRepo; + await itemRepo.Initialize(userDataRepo).ConfigureAwait(false); ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; await ConfigureNotificationsRepository().ConfigureAwait(false); progress.Report(100); @@ -647,15 +650,20 @@ namespace MediaBrowser.Server.Startup.Common /// <returns>Task.</returns> private async Task RegisterMediaEncoder(IProgress<double> progress) { + string encoderPath = null; + string probePath = null; + var info = await new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, NativeApp.Environment, NativeApp.GetFfmpegInstallInfo()) .GetFFMpegInfo(NativeApp.Environment, _startupOptions, progress).ConfigureAwait(false); - _hasExternalEncoder = string.Equals(info.Version, "custom", StringComparison.OrdinalIgnoreCase); + encoderPath = info.EncoderPath; + probePath = info.ProbePath; + _hasExternalEncoder = string.Equals(info.Version, "external", StringComparison.OrdinalIgnoreCase); var mediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), JsonSerializer, - info.EncoderPath, - info.ProbePath, + encoderPath, + probePath, _hasExternalEncoder, ServerConfigurationManager, FileSystemManager, @@ -747,18 +755,6 @@ namespace MediaBrowser.Server.Startup.Common } /// <summary> - /// Configures the user data repositories. - /// </summary> - private async Task ConfigureUserDataRepositories() - { - var repo = new SqliteUserDataRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector()); - - await repo.Initialize().ConfigureAwait(false); - - ((UserDataManager)UserDataManager).Repository = repo; - } - - /// <summary> /// Dirty hacks /// </summary> private void SetStaticProperties() @@ -1145,7 +1141,8 @@ namespace MediaBrowser.Server.Startup.Common ServerName = FriendlyName, LocalAddress = localAddress, SupportsLibraryMonitor = SupportsLibraryMonitor, - HasExternalEncoder = _hasExternalEncoder + HasExternalEncoder = _hasExternalEncoder, + SystemArchitecture = NativeApp.Environment.SystemArchitecture }; } diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs index 2c393ff29..a4c50d0d8 100644 --- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs +++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs @@ -53,13 +53,17 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg { ProbePath = customffProbePath, EncoderPath = customffMpegPath, - Version = "custom" + Version = "external" }; } var downloadInfo = _ffmpegInstallInfo; var version = downloadInfo.Version; + if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase)) + { + return new FFMpegInfo(); + } if (string.Equals(version, "path", StringComparison.OrdinalIgnoreCase)) { @@ -175,18 +179,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg return null; } - private async void DownloadFFMpegInBackground(FFMpegInstallInfo downloadinfo, string directory) - { - try - { - await DownloadFFMpeg(downloadinfo, directory, new Progress<double>()).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading ffmpeg", ex); - } - } - private async Task DownloadFFMpeg(FFMpegInstallInfo downloadinfo, string directory, IProgress<double> progress) { foreach (var url in downloadinfo.DownloadUrls) diff --git a/MediaBrowser.Server.Startup.Common/NativeEnvironment.cs b/MediaBrowser.Server.Startup.Common/NativeEnvironment.cs index 5b45afe73..b30509982 100644 --- a/MediaBrowser.Server.Startup.Common/NativeEnvironment.cs +++ b/MediaBrowser.Server.Startup.Common/NativeEnvironment.cs @@ -1,4 +1,5 @@ - +using MediaBrowser.Model.System; + namespace MediaBrowser.Server.Startup.Common { public class NativeEnvironment @@ -15,11 +16,4 @@ namespace MediaBrowser.Server.Startup.Common Bsd = 2, Linux = 3 } - - public enum Architecture - { - X86 = 0, - X86_X64 = 1, - Arm = 2 - } } diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 35660b2b1..fcd6f7faf 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -83,8 +83,8 @@ <Reference Include="System.Configuration" /> <Reference Include="System.Configuration.Install" /> <Reference Include="System.Core" /> - <Reference Include="System.Data.SQLite, Version=1.0.101.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> - <HintPath>..\packages\System.Data.SQLite.Core.1.0.101.0\lib\net46\System.Data.SQLite.dll</HintPath> + <Reference Include="System.Data.SQLite, Version=1.0.102.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Data.SQLite.Core.1.0.102.0\lib\net46\System.Data.SQLite.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="System.Drawing" /> @@ -1112,12 +1112,12 @@ <PostBuildEvent> </PostBuildEvent> </PropertyGroup> - <Import Project="..\packages\System.Data.SQLite.Core.1.0.101.0\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.101.0\build\net46\System.Data.SQLite.Core.targets')" /> + <Import Project="..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets')" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <PropertyGroup> <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> </PropertyGroup> - <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.101.0\build\net46\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.101.0\build\net46\System.Data.SQLite.Core.targets'))" /> + <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets'))" /> </Target> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index 2ea5064a4..d8b2720c2 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -10,6 +10,7 @@ using System.Reflection; using System.Windows.Forms; using CommonIO; using MediaBrowser.Controller.Power; +using MediaBrowser.Model.System; using MediaBrowser.Server.Implementations.Persistence; using MediaBrowser.Server.Startup.Common.FFMpeg; using OperatingSystem = MediaBrowser.Server.Startup.Common.OperatingSystem; @@ -53,7 +54,7 @@ namespace MediaBrowser.ServerApplication.Native return new NativeEnvironment { OperatingSystem = OperatingSystem.Windows, - SystemArchitecture = System.Environment.Is64BitOperatingSystem ? Architecture.X86_X64 : Architecture.X86, + SystemArchitecture = System.Environment.Is64BitOperatingSystem ? Architecture.X64 : Architecture.X86, OperatingSystemVersionString = System.Environment.OSVersion.VersionString }; } @@ -158,9 +159,7 @@ namespace MediaBrowser.ServerApplication.Native info.FFMpegFilename = "ffmpeg.exe"; info.FFProbeFilename = "ffprobe.exe"; - info.Version = "20160410"; - info.ArchiveType = "7z"; - info.DownloadUrls = GetDownloadUrls(); + info.Version = "0"; return info; } @@ -205,25 +204,5 @@ namespace MediaBrowser.ServerApplication.Native { ((Process)sender).Dispose(); } - - private string[] GetDownloadUrls() - { - switch (Environment.SystemArchitecture) - { - case Architecture.X86_X64: - return new[] - { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win64.7z", - "https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20160409-git-0c90b2e-win64-static.7z" - }; - case Architecture.X86: - return new[] - { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win32.7z", - "https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20160409-git-0c90b2e-win32-static.7z" - }; - } - return new string[] { }; - } } }
\ No newline at end of file diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index 2d4baae25..a573c6fd9 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -3,5 +3,5 @@ <package id="CommonIO" version="1.0.0.9" targetFramework="net45" /> <package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" /> <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" /> - <package id="System.Data.SQLite.Core" version="1.0.101.0" targetFramework="net46" /> + <package id="System.Data.SQLite.Core" version="1.0.102.0" targetFramework="net46" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index bc5db0224..4eae9975a 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -374,6 +374,12 @@ <Content Include="dashboard-ui\scripts\tvlatest.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\wizardcomponents.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\wizardcontroller.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\wizardlivetvguide.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1094,6 +1100,9 @@ <Content Include="dashboard-ui\wizardagreement.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\wizardcomponents.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\wizardlivetvguide.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
|
