diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index dfdd0daf0a..aac7085207 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -46,6 +46,18 @@ namespace MediaBrowser.Api public bool IncludeHidden { get; set; } } + [Route("/Environment/NetworkShares", "GET")] + [Api(Description = "Gets shares from a network device")] + public class GetNetworkShares : IReturn> + { + /// + /// Gets or sets the path. + /// + /// The path. + [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Path { get; set; } + } + /// /// Class GetDrives /// @@ -64,11 +76,25 @@ namespace MediaBrowser.Api { } + [Route("/Environment/ParentPath", "GET")] + [Api(Description = "Gets the parent path of a given path")] + public class GetParentPath : IReturn + { + /// + /// Gets or sets the path. + /// + /// The path. + [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Path { get; set; } + } + /// /// Class EnvironmentService /// public class EnvironmentService : BaseApiService { + const char UncSeparator = '\\'; + /// /// The _network manager /// @@ -105,13 +131,9 @@ namespace MediaBrowser.Api throw new ArgumentNullException("Path"); } - // If it's not a drive trim trailing slashes. - if (!path.EndsWith(":\\")) - { - path = path.TrimEnd('\\'); - } + var networkPrefix = UncSeparator.ToString(CultureInfo.InvariantCulture) + UncSeparator.ToString(CultureInfo.InvariantCulture); - if (path.StartsWith(NetworkPrefix, StringComparison.OrdinalIgnoreCase) && path.LastIndexOf('\\') == 1) + if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase) && path.LastIndexOf(UncSeparator) == 1) { return ToOptimizedResult(GetNetworkShares(path).OrderBy(i => i.Path).ToList()); } @@ -119,6 +141,15 @@ namespace MediaBrowser.Api return ToOptimizedResult(GetFileSystemEntries(request).OrderBy(i => i.Path).ToList()); } + public object Get(GetNetworkShares request) + { + var path = request.Path; + + var shares = GetNetworkShares(path).OrderBy(i => i.Path).ToList(); + + return ToOptimizedResult(shares); + } + /// /// Gets the specified request. /// @@ -154,25 +185,13 @@ namespace MediaBrowser.Api /// System.Object. public object Get(GetNetworkDevices request) { - var result = GetNetworkDevices().OrderBy(i => i.Path).ToList(); + var result = _networkManager.GetNetworkDevices() + .OrderBy(i => i.Path) + .ToList(); return ToOptimizedResult(result); } - /// - /// Gets the network computers. - /// - /// IEnumerable{FileSystemEntryInfo}. - private IEnumerable GetNetworkDevices() - { - return _networkManager.GetNetworkDevices().Select(c => new FileSystemEntryInfo - { - Name = c, - Path = NetworkPrefix + c, - Type = FileSystemEntryType.NetworkComputer - }); - } - /// /// Gets the name. /// @@ -223,7 +242,7 @@ namespace MediaBrowser.Api { return false; } - + return true; }); @@ -236,13 +255,27 @@ namespace MediaBrowser.Api }).ToList(); } - /// - /// Gets the network prefix. - /// - /// The network prefix. - private string NetworkPrefix + public object Get(GetParentPath request) { - get { return Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture) + Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture); } + var parent = Path.GetDirectoryName(request.Path); + + if (string.IsNullOrEmpty(parent)) + { + // Check if unc share + var index = request.Path.LastIndexOf(UncSeparator); + + if (index != -1 && request.Path.IndexOf(UncSeparator) == 0) + { + parent = request.Path.Substring(0, index); + + if (string.IsNullOrWhiteSpace(parent.TrimStart(UncSeparator))) + { + parent = null; + } + } + } + + return parent; } } } diff --git a/MediaBrowser.Api/Images/ImageRequest.cs b/MediaBrowser.Api/Images/ImageRequest.cs index 0d4c1ff1be..718d5f402c 100644 --- a/MediaBrowser.Api/Images/ImageRequest.cs +++ b/MediaBrowser.Api/Images/ImageRequest.cs @@ -59,8 +59,11 @@ namespace MediaBrowser.Api.Images [ApiMember(Name = "AddPlayedIndicator", Description = "Optional. Add a played indicator", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool AddPlayedIndicator { get; set; } - [ApiMember(Name = "PercentPlayed", Description = "Optional percent to render for the percent played overlay", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public int? PercentPlayed { get; set; } + [ApiMember(Name = "PercentPlayed", Description = "Optional percent to render for the percent played overlay", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public double? PercentPlayed { get; set; } + + [ApiMember(Name = "UnplayedCount", Description = "Optional unplayed count overlay to render", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? UnplayedCount { get; set; } [ApiMember(Name = "BackgroundColor", Description = "Optional. Apply a background color for transparent images.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string BackgroundColor { get; set; } diff --git a/MediaBrowser.Api/Images/ImageWriter.cs b/MediaBrowser.Api/Images/ImageWriter.cs index 2ace05125b..2f161709a4 100644 --- a/MediaBrowser.Api/Images/ImageWriter.cs +++ b/MediaBrowser.Api/Images/ImageWriter.cs @@ -91,6 +91,7 @@ namespace MediaBrowser.Api.Images OutputFormat = Request.Format, AddPlayedIndicator = Request.AddPlayedIndicator, PercentPlayed = Request.PercentPlayed, + UnplayedCount = Request.UnplayedCount, BackgroundColor = Request.BackgroundColor }; diff --git a/MediaBrowser.Api/Library/LibraryHelpers.cs b/MediaBrowser.Api/Library/LibraryHelpers.cs index fb986605c0..e40cb7dd46 100644 --- a/MediaBrowser.Api/Library/LibraryHelpers.cs +++ b/MediaBrowser.Api/Library/LibraryHelpers.cs @@ -65,17 +65,10 @@ namespace MediaBrowser.Api.Library throw new DirectoryNotFoundException("The path does not exist."); } - // Strip off trailing slash, but not on drives - path = path.TrimEnd(Path.DirectorySeparatorChar); - if (path.EndsWith(":", StringComparison.OrdinalIgnoreCase)) - { - path += Path.DirectorySeparatorChar; - } - var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); - ValidateNewMediaPath(fileSystem, rootFolderPath, path, appPaths); + ValidateNewMediaPath(fileSystem, rootFolderPath, path); var shortcutFilename = Path.GetFileNameWithoutExtension(path); @@ -96,25 +89,18 @@ namespace MediaBrowser.Api.Library /// The file system. /// The current view root folder path. /// The media path. - /// The app paths. /// /// - private static void ValidateNewMediaPath(IFileSystem fileSystem, string currentViewRootFolderPath, string mediaPath, IServerApplicationPaths appPaths) + private static void ValidateNewMediaPath(IFileSystem fileSystem, string currentViewRootFolderPath, string mediaPath) { - var duplicate = Directory.EnumerateFiles(appPaths.RootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories) - .Select(fileSystem.ResolveShortcut) - .FirstOrDefault(p => !IsNewPathValid(mediaPath, p, false)); - - if (!string.IsNullOrEmpty(duplicate)) - { - throw new ArgumentException(string.Format("The path cannot be added to the library because {0} already exists.", duplicate)); - } + var pathsInCurrentVIew = Directory.EnumerateFiles(currentViewRootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories) + .Select(fileSystem.ResolveShortcut) + .ToList(); // Don't allow duplicate sub-paths within the same user library, or it will result in duplicate items // See comments in IsNewPathValid - duplicate = Directory.EnumerateFiles(currentViewRootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories) - .Select(fileSystem.ResolveShortcut) - .FirstOrDefault(p => !IsNewPathValid(mediaPath, p, true)); + var duplicate = pathsInCurrentVIew + .FirstOrDefault(p => !IsNewPathValid(fileSystem, mediaPath, p)); if (!string.IsNullOrEmpty(duplicate)) { @@ -122,9 +108,8 @@ namespace MediaBrowser.Api.Library } // Make sure the current root folder doesn't already have a shortcut to the same path - duplicate = Directory.EnumerateFiles(currentViewRootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories) - .Select(fileSystem.ResolveShortcut) - .FirstOrDefault(p => mediaPath.Equals(p, StringComparison.OrdinalIgnoreCase)); + duplicate = pathsInCurrentVIew + .FirstOrDefault(p => string.Equals(mediaPath, p, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrEmpty(duplicate)) { @@ -135,30 +120,30 @@ namespace MediaBrowser.Api.Library /// /// Validates that a new path can be added based on an existing path /// + /// The file system. /// The new path. /// The existing path. - /// if set to true [enforce sub path restriction]. /// true if [is new path valid] [the specified new path]; otherwise, false. - private static bool IsNewPathValid(string newPath, string existingPath, bool enforceSubPathRestriction) + private static bool IsNewPathValid(IFileSystem fileSystem, string newPath, string existingPath) { // Example: D:\Movies is the existing path // D:\ cannot be added // Neither can D:\Movies\Kids // A D:\Movies duplicate is ok here since that will be caught later - if (newPath.Equals(existingPath, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(newPath, existingPath, StringComparison.OrdinalIgnoreCase)) { return true; } // If enforceSubPathRestriction is true, validate the D:\Movies\Kids scenario - if (enforceSubPathRestriction && newPath.StartsWith(existingPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) + if (fileSystem.ContainsSubPath(existingPath, newPath)) { return false; } // Validate the D:\ scenario - if (existingPath.StartsWith(newPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) + if (fileSystem.ContainsSubPath(newPath, existingPath)) { return false; } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 61883ddaae..ae9c79c0e8 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -61,6 +61,9 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } + + [ApiMember(Name = "IsRecording", Description = "Optional filter by recordings that are currently active, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsRecording { get; set; } } [Route("/LiveTv/Recordings/Groups", "GET")] @@ -274,7 +277,8 @@ namespace MediaBrowser.Api.LiveTv UserId = request.UserId, GroupId = request.GroupId, StartIndex = request.StartIndex, - Limit = request.Limit + Limit = request.Limit, + IsRecording = request.IsRecording }, CancellationToken.None).Result; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 1e2ae58b25..999260b8d2 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -197,10 +197,6 @@ namespace MediaBrowser.Api.Playback { args += string.Format("-map 0:{0}", state.VideoStream.Index); } - else if (!state.HasMediaStreams) - { - args += string.Format("-map 0:{0}", 0); - } else { args += "-map -0:v"; @@ -210,10 +206,6 @@ namespace MediaBrowser.Api.Playback { args += string.Format(" -map 0:{0}", state.AudioStream.Index); } - else if (!state.HasMediaStreams) - { - args += string.Format(" -map 0:{0}", 1); - } else { @@ -871,7 +863,7 @@ namespace MediaBrowser.Api.Playback RequestedUrl = url }; - BaseItem item; + Guid itemId; if (string.Equals(request.Type, "Recording", StringComparison.OrdinalIgnoreCase)) { @@ -900,7 +892,7 @@ namespace MediaBrowser.Api.Playback state.IsRemote = true; } - item = recording; + itemId = recording.Id; } else if (string.Equals(request.Type, "Channel", StringComparison.OrdinalIgnoreCase)) { @@ -916,11 +908,11 @@ namespace MediaBrowser.Api.Playback state.IsRemote = true; - item = channel; + itemId = channel.Id; } else { - item = DtoService.GetItemByDtoId(request.Id); + var item = DtoService.GetItemByDtoId(request.Id); state.MediaPath = item.Path; state.IsRemote = item.LocationType == LocationType.Remote; @@ -937,13 +929,15 @@ namespace MediaBrowser.Api.Playback ? new List() : video.PlayableStreamFileNames.ToList(); } + + itemId = item.Id; } var videoRequest = request as VideoStreamRequest; var mediaStreams = ItemRepository.GetMediaStreams(new MediaStreamQuery { - ItemId = item.Id + ItemId = itemId }).ToList(); diff --git a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs index 616981d508..d00494bace 100644 --- a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs +++ b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs @@ -82,6 +82,16 @@ namespace MediaBrowser.Common.Implementations.IO throw new ArgumentNullException("target"); } + if (string.IsNullOrEmpty(shortcutPath)) + { + throw new ArgumentNullException("shortcutPath"); + } + + if (string.IsNullOrEmpty(target)) + { + throw new ArgumentNullException("target"); + } + File.WriteAllText(shortcutPath, target); } @@ -92,6 +102,11 @@ namespace MediaBrowser.Common.Implementations.IO /// FileSystemInfo. public FileSystemInfo GetFileSystemInfo(string path) { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + // Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists if (Path.HasExtension(path)) { @@ -172,7 +187,6 @@ namespace MediaBrowser.Common.Implementations.IO /// Gets the creation time UTC. /// /// The info. - /// The logger. /// DateTime. public DateTime GetLastWriteTimeUtc(FileSystemInfo info) { @@ -224,6 +238,16 @@ namespace MediaBrowser.Common.Implementations.IO /// The file2. public void SwapFiles(string file1, string file2) { + if (string.IsNullOrEmpty(file1)) + { + throw new ArgumentNullException("file1"); + } + + if (string.IsNullOrEmpty(file2)) + { + throw new ArgumentNullException("file2"); + } + var temp1 = Path.GetTempFileName(); var temp2 = Path.GetTempFileName(); @@ -247,6 +271,11 @@ namespace MediaBrowser.Common.Implementations.IO /// The path. private void RemoveHiddenAttribute(string path) { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + var currentFile = new FileInfo(path); // This will fail if the file is hidden @@ -258,127 +287,52 @@ namespace MediaBrowser.Common.Implementations.IO } } } - } - /// - /// Adapted from http://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java - /// - internal class WindowsShortcut - { - public bool IsDirectory { get; private set; } - public bool IsLocal { get; private set; } - public string ResolvedPath { get; private set; } - - public WindowsShortcut(string file) + public bool ContainsSubPath(string parentPath, string path) { - ParseLink(File.ReadAllBytes(file), Encoding.UTF8); - } - - private static bool isMagicPresent(byte[] link) - { - - const int magic = 0x0000004C; - const int magic_offset = 0x00; - - return link.Length >= 32 && bytesToDword(link, magic_offset) == magic; - } - - /** - * Gobbles up link data by parsing it and storing info in member fields - * @param link all the bytes from the .lnk file - */ - private void ParseLink(byte[] link, Encoding encoding) - { - if (!isMagicPresent(link)) - throw new IOException("Invalid shortcut; magic is missing", 0); - - // get the flags byte - byte flags = link[0x14]; - - // get the file attributes byte - const int file_atts_offset = 0x18; - byte file_atts = link[file_atts_offset]; - byte is_dir_mask = (byte)0x10; - if ((file_atts & is_dir_mask) > 0) - { - IsDirectory = true; - } - else + if (string.IsNullOrEmpty(parentPath)) { - IsDirectory = false; + throw new ArgumentNullException("parentPath"); } - // if the shell settings are present, skip them - const int shell_offset = 0x4c; - const byte has_shell_mask = (byte)0x01; - int shell_len = 0; - if ((flags & has_shell_mask) > 0) + if (string.IsNullOrEmpty(path)) { - // the plus 2 accounts for the length marker itself - shell_len = bytesToWord(link, shell_offset) + 2; + throw new ArgumentNullException("path"); } + + return path.IndexOf(parentPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) != -1; + } - // get to the file settings - int file_start = 0x4c + shell_len; - - const int file_location_info_flag_offset_offset = 0x08; - int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset]; - IsLocal = (file_location_info_flag & 2) == 0; - // get the local volume and local system values - //final int localVolumeTable_offset_offset = 0x0C; - const int basename_offset_offset = 0x10; - const int networkVolumeTable_offset_offset = 0x14; - const int finalname_offset_offset = 0x18; - int finalname_offset = link[file_start + finalname_offset_offset] + file_start; - String finalname = getNullDelimitedString(link, finalname_offset, encoding); - if (IsLocal) + public bool IsRootPath(string path) + { + if (string.IsNullOrEmpty(path)) { - int basename_offset = link[file_start + basename_offset_offset] + file_start; - String basename = getNullDelimitedString(link, basename_offset, encoding); - ResolvedPath = basename + finalname; + throw new ArgumentNullException("path"); } - else + + var parent = Path.GetDirectoryName(path); + + if (!string.IsNullOrEmpty(parent)) { - int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start; - int shareName_offset_offset = 0x08; - int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset] - + networkVolumeTable_offset; - String shareName = getNullDelimitedString(link, shareName_offset, encoding); - ResolvedPath = shareName + "\\" + finalname; + return false; } + + return true; } - private static string getNullDelimitedString(byte[] bytes, int off, Encoding encoding) + public string NormalizePath(string path) { - int len = 0; - - // count bytes until the null character (0) - while (true) + if (string.IsNullOrEmpty(path)) { - if (bytes[off + len] == 0) - { - break; - } - len++; + throw new ArgumentNullException("path"); } - return encoding.GetString(bytes, off, len); - } - - /* - * convert two bytes into a short note, this is little endian because it's - * for an Intel only OS. - */ - private static int bytesToWord(byte[] bytes, int off) - { - return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); - } + if (path.EndsWith(":\\", StringComparison.OrdinalIgnoreCase)) + { + return path; + } - private static int bytesToDword(byte[] bytes, int off) - { - return (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off); + return path.TrimEnd(Path.DirectorySeparatorChar); } - } - } diff --git a/MediaBrowser.Common/IO/IFileSystem.cs b/MediaBrowser.Common/IO/IFileSystem.cs index 8fba631955..f721e80eac 100644 --- a/MediaBrowser.Common/IO/IFileSystem.cs +++ b/MediaBrowser.Common/IO/IFileSystem.cs @@ -81,5 +81,27 @@ namespace MediaBrowser.Common.IO /// The file1. /// The file2. void SwapFiles(string file1, string file2); + + /// + /// Determines whether [contains sub path] [the specified parent path]. + /// + /// The parent path. + /// The path. + /// true if [contains sub path] [the specified parent path]; otherwise, false. + bool ContainsSubPath(string parentPath, string path); + + /// + /// Determines whether [is root path] [the specified path]. + /// + /// The path. + /// true if [is root path] [the specified path]; otherwise, false. + bool IsRootPath(string path); + + /// + /// Normalizes the path. + /// + /// The path. + /// System.String. + string NormalizePath(string path); } } diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 0d644b9096..b5f1c82941 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -1,3 +1,4 @@ +using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using System.Collections.Generic; using System.Net; @@ -35,7 +36,7 @@ namespace MediaBrowser.Common.Net /// Gets available devices within the domain /// /// PC's in the Domain - IEnumerable GetNetworkDevices(); + IEnumerable GetNetworkDevices(); /// /// Parses the specified endpointstring. diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 506d6fd3d2..93293b34d6 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -37,8 +37,10 @@ namespace MediaBrowser.Controller.Drawing public bool AddPlayedIndicator { get; set; } - public int? PercentPlayed { get; set; } + public int? UnplayedCount { get; set; } + public double? PercentPlayed { get; set; } + public string BackgroundColor { get; set; } public bool HasDefaultOptions() @@ -56,6 +58,7 @@ namespace MediaBrowser.Controller.Drawing IsOutputFormatDefault && !AddPlayedIndicator && !PercentPlayed.HasValue && + !UnplayedCount.HasValue && string.IsNullOrEmpty(BackgroundColor); } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 11562b3d72..dbda4a243b 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -156,7 +156,7 @@ namespace MediaBrowser.Controller.Entities public DateTime DateModified { get; set; } public DateTime DateLastSaved { get; set; } - + /// /// The logger /// @@ -327,21 +327,18 @@ namespace MediaBrowser.Controller.Entities // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - args.FileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); + var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); // Need to remove subpaths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action if (isPhysicalRoot) { - var paths = args.FileSystemDictionary.Keys.ToList(); + var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys); - foreach (var subPath in paths - .Where(subPath => !subPath.EndsWith(":\\", StringComparison.OrdinalIgnoreCase) && paths.Any(i => subPath.StartsWith(i.TrimEnd(System.IO.Path.DirectorySeparatorChar) + System.IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))) - { - Logger.Info("Ignoring duplicate path: {0}", subPath); - args.FileSystemDictionary.Remove(subPath); - } + fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName); } + + args.FileSystemDictionary = fileSystemDictionary; } //update our dates @@ -1016,14 +1013,18 @@ namespace MediaBrowser.Controller.Entities return lang; } + public virtual bool IsSaveLocalMetadataEnabled() + { + return ConfigurationManager.Configuration.SaveLocalMeta; + } + /// /// Determines if a given user has access to this item /// /// The user. - /// The localization manager. /// true if [is parental allowed] [the specified user]; otherwise, false. /// user - public bool IsParentalAllowed(User user, ILocalizationManager localizationManager) + public bool IsParentalAllowed(User user) { if (user == null) { @@ -1049,7 +1050,7 @@ namespace MediaBrowser.Controller.Entities return !GetBlockUnratedValue(user.Configuration); } - var value = localizationManager.GetRatingLevel(rating); + var value = LocalizationManager.GetRatingLevel(rating); // Could not determine the integer value if (!value.HasValue) @@ -1084,7 +1085,7 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException("user"); } - return IsParentalAllowed(user, LocalizationManager); + return IsParentalAllowed(user); } /// diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 912b8fa93c..a85157a266 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -519,85 +519,84 @@ namespace MediaBrowser.Controller.Entities await Task.WhenAll(tasks).ConfigureAwait(false); } - Tuple currentTuple = tuple; - - tasks.Add(Task.Run(async () => - { - cancellationToken.ThrowIfCancellationRequested(); - - var child = currentTuple.Item1; - try - { - //refresh it - await child.RefreshMetadata(cancellationToken, forceSave: currentTuple.Item2, forceRefresh: forceRefreshMetadata, resetResolveArgs: false).ConfigureAwait(false); - } - catch (IOException ex) - { - Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name); - } + tasks.Add(RefreshChild(tuple, progress, percentages, list.Count, cancellationToken, recursive, forceRefreshMetadata)); + } - // Refresh children if a folder and the item changed or recursive is set to true - var refreshChildren = child.IsFolder && (currentTuple.Item2 || (recursive.HasValue && recursive.Value)); + cancellationToken.ThrowIfCancellationRequested(); - if (refreshChildren) - { - // Don't refresh children if explicitly set to false - if (recursive.HasValue && recursive.Value == false) - { - refreshChildren = false; - } - } + await Task.WhenAll(tasks).ConfigureAwait(false); + } - if (refreshChildren) - { - cancellationToken.ThrowIfCancellationRequested(); + private async Task RefreshChild(Tuple currentTuple, IProgress progress, Dictionary percentages, int childCount, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false) + { + cancellationToken.ThrowIfCancellationRequested(); - var innerProgress = new ActionableProgress(); + var child = currentTuple.Item1; + try + { + //refresh it + await child.RefreshMetadata(cancellationToken, forceSave: currentTuple.Item2, forceRefresh: forceRefreshMetadata, resetResolveArgs: false).ConfigureAwait(false); + } + catch (IOException ex) + { + Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name); + } - innerProgress.RegisterAction(p => - { - lock (percentages) - { - percentages[child.Id] = p / 100; + // Refresh children if a folder and the item changed or recursive is set to true + var refreshChildren = child.IsFolder && (currentTuple.Item2 || (recursive.HasValue && recursive.Value)); - var percent = percentages.Values.Sum(); - percent /= list.Count; + if (refreshChildren) + { + // Don't refresh children if explicitly set to false + if (recursive.HasValue && recursive.Value == false) + { + refreshChildren = false; + } + } - progress.Report((90 * percent) + 10); - } - }); + if (refreshChildren) + { + cancellationToken.ThrowIfCancellationRequested(); - await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); + var innerProgress = new ActionableProgress(); - try - { - // Some folder providers are unable to refresh until children have been refreshed. - await child.RefreshMetadata(cancellationToken, resetResolveArgs: false).ConfigureAwait(false); - } - catch (IOException ex) - { - Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name); - } - } - else + innerProgress.RegisterAction(p => + { + lock (percentages) { - lock (percentages) - { - percentages[child.Id] = 1; + percentages[child.Id] = p / 100; - var percent = percentages.Values.Sum(); - percent /= list.Count; + var percent = percentages.Values.Sum(); + percent /= childCount; - progress.Report((90 * percent) + 10); - } + progress.Report((90 * percent) + 10); } + }); - }, cancellationToken)); + await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); + + try + { + // Some folder providers are unable to refresh until children have been refreshed. + await child.RefreshMetadata(cancellationToken, resetResolveArgs: false).ConfigureAwait(false); + } + catch (IOException ex) + { + Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name); + } } + else + { + lock (percentages) + { + percentages[child.Id] = 1; - cancellationToken.ThrowIfCancellationRequested(); + var percent = percentages.Values.Sum(); + percent /= childCount; - await Task.WhenAll(tasks).ConfigureAwait(false); + progress.Report((90 * percent) + 10); + } + } } /// @@ -646,7 +645,7 @@ namespace MediaBrowser.Controller.Entities private bool ContainsPath(string parent, string path) { - return string.Equals(parent, path, StringComparison.OrdinalIgnoreCase) || path.IndexOf(parent.TrimEnd(System.IO.Path.DirectorySeparatorChar) + System.IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) != -1; + return string.Equals(parent, path, StringComparison.OrdinalIgnoreCase) || FileSystem.ContainsSubPath(parent, path); } /// diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index d800acd9bd..a7cd76a66a 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -16,8 +16,8 @@ namespace MediaBrowser.Controller.Entities /// Gets the path. /// /// The path. - string Path { get; } - + string Path { get; set; } + /// /// Gets the identifier. /// @@ -100,7 +100,7 @@ namespace MediaBrowser.Controller.Entities { return item.HasImage(imageType, 0); } - + /// /// Sets the image path. /// diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index ae34621cb8..036ac7e81e 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -320,5 +320,12 @@ namespace MediaBrowser.Controller.Library /// The items. /// IEnumerable{System.String}. IEnumerable GetAllArtists(IEnumerable items); + + /// + /// Normalizes the root path list. + /// + /// The paths. + /// IEnumerable{System.String}. + IEnumerable NormalizeRootPathList(IEnumerable paths); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index c26e29d942..87ac0d4dc6 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -153,7 +153,7 @@ namespace MediaBrowser.Controller.LiveTv /// The identifier. /// The cancellation token. /// LiveTvRecording. - Task GetInternalRecording(string id, CancellationToken cancellationToken); + Task GetInternalRecording(string id, CancellationToken cancellationToken); /// /// Gets the recording stream. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs new file mode 100644 index 0000000000..d9bceb6cad --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs @@ -0,0 +1,26 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.LiveTv +{ + public interface ILiveTvRecording : IHasImages, IHasMediaStreams + { + string ServiceName { get; set; } + + string MediaType { get; } + + LocationType LocationType { get; } + + RecordingInfo RecordingInfo { get; set; } + + string GetClientTypeName(); + + string GetUserDataKey(); + + bool IsParentalAllowed(User user); + + Task RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true); + } +} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs new file mode 100644 index 0000000000..8676540fd0 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs @@ -0,0 +1,52 @@ +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.LiveTv +{ + public class LiveTvAudioRecording : Audio, ILiveTvRecording + { + /// + /// Gets the user data key. + /// + /// System.String. + public override string GetUserDataKey() + { + return GetClientTypeName() + "-" + Name; + } + + public RecordingInfo RecordingInfo { get; set; } + + public string ServiceName { get; set; } + + public override string MediaType + { + get + { + return Model.Entities.MediaType.Audio; + } + } + + public override LocationType LocationType + { + get + { + if (!string.IsNullOrEmpty(Path)) + { + return base.LocationType; + } + + return LocationType.Remote; + } + } + + public override string GetClientTypeName() + { + return "Recording"; + } + + public override bool IsSaveLocalMetadataEnabled() + { + return false; + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs similarity index 70% rename from MediaBrowser.Controller/LiveTv/LiveTvRecording.cs rename to MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs index 1c453ab5a1..9dfc7f828d 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs @@ -1,10 +1,9 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.LiveTv; namespace MediaBrowser.Controller.LiveTv { - public class LiveTvRecording : BaseItem + public class LiveTvVideoRecording : Video, ILiveTvRecording { /// /// Gets the user data key. @@ -23,7 +22,7 @@ namespace MediaBrowser.Controller.LiveTv { get { - return RecordingInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; + return Model.Entities.MediaType.Video; } } @@ -31,6 +30,11 @@ namespace MediaBrowser.Controller.LiveTv { get { + if (!string.IsNullOrEmpty(Path)) + { + return base.LocationType; + } + return LocationType.Remote; } } @@ -39,5 +43,10 @@ namespace MediaBrowser.Controller.LiveTv { return "Recording"; } + + public override bool IsSaveLocalMetadataEnabled() + { + return false; + } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 0c5c0a5cd5..25ea9979c3 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -108,6 +108,8 @@ + + @@ -115,7 +117,7 @@ - + diff --git a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs index 47accbec54..0ef5c9dc01 100644 --- a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs @@ -51,6 +51,18 @@ namespace MediaBrowser.Model.LiveTv /// public string Name { get; set; } + /// + /// Gets or sets the type of the location. + /// + /// The type of the location. + public LocationType LocationType { get; set; } + + /// + /// Gets or sets the media streams. + /// + /// The media streams. + public List MediaStreams { get; set; } + /// /// Gets or sets the path. /// diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index e63a250e6e..7312476722 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -40,6 +40,12 @@ /// /// The limit. public int? Limit { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is recording. + /// + /// null if [is recording] contains no value, true if [is recording]; otherwise, false. + public bool? IsRecording { get; set; } } public class RecordingGroupQuery diff --git a/MediaBrowser.Mono.userprefs b/MediaBrowser.Mono.userprefs index 1815e61ca3..51af96f9ff 100644 --- a/MediaBrowser.Mono.userprefs +++ b/MediaBrowser.Mono.userprefs @@ -1,8 +1,9 @@  - + - + + diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 5782e3e635..fd78a75656 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -191,7 +191,7 @@ namespace MediaBrowser.Providers.MediaInfo var filename = item.Album ?? string.Empty; filename += item.Artists.FirstOrDefault() ?? string.Empty; - filename += album == null ? item.Id.ToString("N") + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary"; + filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary"; filename = filename.GetMD5() + ".jpg"; diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index b248fcb40b..dc7c7c139e 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -85,15 +85,6 @@ namespace MediaBrowser.Providers.Music return item is MusicArtist; } - /// - /// Gets a value indicating whether [save local meta]. - /// - /// true if [save local meta]; otherwise, false. - protected virtual bool SaveLocalMeta - { - get { return ConfigurationManager.Configuration.SaveLocalMeta; } - } - /// /// Gets a value indicating whether [refresh on version change]. /// diff --git a/MediaBrowser.Providers/Music/LastfmBaseProvider.cs b/MediaBrowser.Providers/Music/LastfmBaseProvider.cs index 26796f7e44..cab9e7e39c 100644 --- a/MediaBrowser.Providers/Music/LastfmBaseProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmBaseProvider.cs @@ -68,14 +68,6 @@ namespace MediaBrowser.Providers.Music /// The HTTP client. protected IHttpClient HttpClient { get; private set; } - protected virtual bool SaveLocalMeta - { - get - { - return ConfigurationManager.Configuration.SaveLocalMeta; - } - } - /// /// Gets a value indicating whether [requires internet]. /// diff --git a/MediaBrowser.Providers/Music/LastfmHelper.cs b/MediaBrowser.Providers/Music/LastfmHelper.cs index df02cee5b9..1b8d0c5f6d 100644 --- a/MediaBrowser.Providers/Music/LastfmHelper.cs +++ b/MediaBrowser.Providers/Music/LastfmHelper.cs @@ -31,11 +31,6 @@ namespace MediaBrowser.Providers.Music artist.ProductionYear = yearFormed; } - - if (data.tags != null && !artist.LockedFields.Contains(MetadataFields.Tags)) - { - AddTags(artist, data.tags); - } string imageSize; artist.LastFmImageUrl = GetImageUrl(data, out imageSize); @@ -100,11 +95,6 @@ namespace MediaBrowser.Providers.Music } } - if (data.toptags != null && !item.LockedFields.Contains(MetadataFields.Tags)) - { - AddTags(item, data.toptags); - } - var album = (MusicAlbum)item; string imageSize; @@ -112,16 +102,5 @@ namespace MediaBrowser.Providers.Music album.LastFmImageUrl = GetImageUrl(data, out imageSize); album.LastFmImageSize = imageSize; } - - private static void AddTags(BaseItem item, LastfmTags tags) - { - var itemTags = (from tag in tags.tag where !string.IsNullOrEmpty(tag.name) select tag.name).ToList(); - - var hasTags = item as IHasTags; - if (hasTags != null) - { - hasTags.Tags = itemTags; - } - } } } diff --git a/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs b/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs index 8dd6940c58..5e4401cfff 100644 --- a/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { return item is MusicAlbum; } diff --git a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs index 924a5a88ea..6b801f1e26 100644 --- a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { if (item is MusicArtist) { diff --git a/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs b/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs index c2e607f6a6..712276bcd6 100644 --- a/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { return item is BoxSet; } diff --git a/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs index 91e7699948..4e2139bb60 100644 --- a/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { return item is Episode; } diff --git a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs index 9c1e6f38e0..7ad13905d6 100644 --- a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { if (!(item is Series) && !(item is BoxSet) && !(item is MusicArtist) && !(item is MusicAlbum) && !(item is Season)) diff --git a/MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs b/MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs index 496bb51d0d..7089b8e1b0 100644 --- a/MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { return item is GameSystem; } diff --git a/MediaBrowser.Providers/Savers/GameXmlSaver.cs b/MediaBrowser.Providers/Savers/GameXmlSaver.cs index 03c3a29165..5dd7617e09 100644 --- a/MediaBrowser.Providers/Savers/GameXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/GameXmlSaver.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { return item is Game; } diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs index f10e24dc15..3fc4579e38 100644 --- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { var trailer = item as Trailer; diff --git a/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs b/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs index c96f71b0a9..0a9f2e4f62 100644 --- a/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { return item is Season; } diff --git a/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs index 7f37ba1ced..131d568a1c 100644 --- a/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Savers var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; // If new metadata has been downloaded and save local is on - if (_config.Configuration.SaveLocalMeta && (wasMetadataEdited || wasMetadataDownloaded)) + if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { return item is Series; } diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 7ddf63cf8c..0568f681a4 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -172,7 +172,7 @@ namespace MediaBrowser.Server.Implementations.Drawing var quality = options.Quality ?? 90; - var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, options.OutputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.BackgroundColor); + var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, options.OutputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor); try { @@ -241,7 +241,9 @@ namespace MediaBrowser.Server.Implementations.Drawing thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality; thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic; thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality; - thumbnailGraph.CompositingMode = string.IsNullOrEmpty(options.BackgroundColor) && !options.PercentPlayed.HasValue && !options.AddPlayedIndicator ? CompositingMode.SourceCopy : CompositingMode.SourceOver; + thumbnailGraph.CompositingMode = string.IsNullOrEmpty(options.BackgroundColor) && !options.UnplayedCount.HasValue && !options.AddPlayedIndicator && !options.PercentPlayed.HasValue ? + CompositingMode.SourceCopy : + CompositingMode.SourceOver; SetBackgroundColor(thumbnailGraph, options); @@ -347,28 +349,31 @@ namespace MediaBrowser.Server.Implementations.Drawing /// The options. private void DrawIndicator(Graphics graphics, int imageWidth, int imageHeight, ImageProcessingOptions options) { - if (!options.AddPlayedIndicator && !options.PercentPlayed.HasValue) + if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && !options.PercentPlayed.HasValue) { return; } try { - var percentOffset = 0; - if (options.AddPlayedIndicator) { var currentImageSize = new Size(imageWidth, imageHeight); - new WatchedIndicatorDrawer().Process(graphics, currentImageSize); + new PlayedIndicatorDrawer().DrawPlayedIndicator(graphics, currentImageSize); + } + else if (options.UnplayedCount.HasValue) + { + var currentImageSize = new Size(imageWidth, imageHeight); - percentOffset = 0 - WatchedIndicatorDrawer.IndicatorWidth; + new UnplayedCountIndicator().DrawUnplayedCountIndicator(graphics, currentImageSize, options.UnplayedCount.Value); } + if (options.PercentPlayed.HasValue) { var currentImageSize = new Size(imageWidth, imageHeight); - new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed.Value, percentOffset); + new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed.Value); } } catch (Exception ex) @@ -465,10 +470,15 @@ namespace MediaBrowser.Server.Implementations.Drawing return new Tuple(croppedImagePath, _fileSystem.GetLastWriteTimeUtc(croppedImagePath)); } + /// + /// Increment this when indicator drawings change + /// + private const string IndicatorVersion = "1"; + /// /// Gets the cache file path based on a set of parameters /// - private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, bool addPlayedIndicator, int? percentPlayed, string backgroundColor) + private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, bool addPlayedIndicator, double? percentPlayed, int? unwatchedCount, string backgroundColor) { var filename = originalPath; @@ -485,16 +495,31 @@ namespace MediaBrowser.Server.Implementations.Drawing filename += "f=" + format; } + var hasIndicator = false; + if (addPlayedIndicator) { filename += "pl=true"; + hasIndicator = true; } if (percentPlayed.HasValue) { filename += "p=" + percentPlayed.Value; + hasIndicator = true; } + if (unwatchedCount.HasValue) + { + filename += "p=" + unwatchedCount.Value; + hasIndicator = true; + } + + if (hasIndicator) + { + filename += "iv=" + IndicatorVersion; + } + if (!string.IsNullOrEmpty(backgroundColor)) { filename += "b=" + backgroundColor; diff --git a/MediaBrowser.Server.Implementations/Drawing/PercentPlayedDrawer.cs b/MediaBrowser.Server.Implementations/Drawing/PercentPlayedDrawer.cs index e2f5b6129c..c621ace438 100644 --- a/MediaBrowser.Server.Implementations/Drawing/PercentPlayedDrawer.cs +++ b/MediaBrowser.Server.Implementations/Drawing/PercentPlayedDrawer.cs @@ -1,36 +1,34 @@ -using System.Drawing; -using System.Globalization; +using System; +using System.Drawing; namespace MediaBrowser.Server.Implementations.Drawing { public class PercentPlayedDrawer { - private const int IndicatorWidth = 80; - private const int IndicatorHeight = 50; - private const int FontSize = 30; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private const int IndicatorHeight = 10; - public void Process(Graphics graphics, Size imageSize, int percent, int rightOffset) + public void Process(Graphics graphics, Size imageSize, double percent) { - var x = imageSize.Width - IndicatorWidth + rightOffset; + var y = imageSize.Height - IndicatorHeight; - using (var backdroundBrush = new SolidBrush(Color.FromArgb(225, 102, 192, 16))) + using (var backdroundBrush = new SolidBrush(Color.FromArgb(225, 0, 0, 0))) { - graphics.FillRectangle(backdroundBrush, x, 0, IndicatorWidth, IndicatorHeight); + const int innerX = 0; + var innerY = y; + var innerWidth = imageSize.Width; + var innerHeight = imageSize.Height; - var text = string.Format("{0}%", percent.ToString(_usCulture)); + graphics.FillRectangle(backdroundBrush, innerX, innerY, innerWidth, innerHeight); - x = imageSize.Width - (percent < 10 ? 66 : 75) + rightOffset; - - using (var font = new Font(FontFamily.GenericSansSerif, FontSize, FontStyle.Regular, GraphicsUnit.Pixel)) + using (var foregroundBrush = new SolidBrush(Color.FromArgb(82, 181, 75))) { - using (var fontBrush = new SolidBrush(Color.White)) - { - graphics.DrawString(text, font, fontBrush, x, 6); - } + double foregroundWidth = innerWidth; + foregroundWidth *= percent; + foregroundWidth /= 100; + + graphics.FillRectangle(foregroundBrush, innerX, innerY, Convert.ToInt32(Math.Round(foregroundWidth)), innerHeight); } } - } } } diff --git a/MediaBrowser.Server.Implementations/Drawing/WatchedIndicatorDrawer.cs b/MediaBrowser.Server.Implementations/Drawing/PlayedIndicatorDrawer.cs similarity index 51% rename from MediaBrowser.Server.Implementations/Drawing/WatchedIndicatorDrawer.cs rename to MediaBrowser.Server.Implementations/Drawing/PlayedIndicatorDrawer.cs index c889db3044..7be1873966 100644 --- a/MediaBrowser.Server.Implementations/Drawing/WatchedIndicatorDrawer.cs +++ b/MediaBrowser.Server.Implementations/Drawing/PlayedIndicatorDrawer.cs @@ -2,33 +2,31 @@ namespace MediaBrowser.Server.Implementations.Drawing { - public class WatchedIndicatorDrawer + public class PlayedIndicatorDrawer { private const int IndicatorHeight = 50; public const int IndicatorWidth = 50; private const int FontSize = 50; + private const int OffsetFromTopRightCorner = 10; - public void Process(Graphics graphics, Size imageSize) + public void DrawPlayedIndicator(Graphics graphics, Size imageSize) { - var x = imageSize.Width - IndicatorWidth; + var x = imageSize.Width - IndicatorWidth - OffsetFromTopRightCorner; - using (var backdroundBrush = new SolidBrush(Color.FromArgb(225, 204, 51, 51))) + using (var backdroundBrush = new SolidBrush(Color.FromArgb(225, 82, 181, 75))) { - graphics.FillRectangle(backdroundBrush, x, 0, IndicatorWidth, IndicatorHeight); + graphics.FillEllipse(backdroundBrush, x, OffsetFromTopRightCorner, IndicatorWidth, IndicatorHeight); - const string text = "a"; - - x = imageSize.Width - 55; + x = imageSize.Width - 55 - OffsetFromTopRightCorner; using (var font = new Font("Webdings", FontSize, FontStyle.Regular, GraphicsUnit.Pixel)) { using (var fontBrush = new SolidBrush(Color.White)) { - graphics.DrawString(text, font, fontBrush, x, -2); + graphics.DrawString("a", font, fontBrush, x, OffsetFromTopRightCorner - 2); } } } - } } } diff --git a/MediaBrowser.Server.Implementations/Drawing/UnplayedCountIndicator.cs b/MediaBrowser.Server.Implementations/Drawing/UnplayedCountIndicator.cs new file mode 100644 index 0000000000..11d812a431 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Drawing/UnplayedCountIndicator.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Drawing +{ + public class UnplayedCountIndicator + { + private const int IndicatorHeight = 50; + public const int IndicatorWidth = 50; + private const int OffsetFromTopRightCorner = 10; + + public void DrawUnplayedCountIndicator(Graphics graphics, Size imageSize, int count) + { + var x = imageSize.Width - IndicatorWidth - OffsetFromTopRightCorner; + + using (var backdroundBrush = new SolidBrush(Color.FromArgb(225, 82, 181, 75))) + { + graphics.FillEllipse(backdroundBrush, x, OffsetFromTopRightCorner, IndicatorWidth, IndicatorHeight); + + var text = count.ToString(); + + x = imageSize.Width - 50 - OffsetFromTopRightCorner; + var y = OffsetFromTopRightCorner + 7; + var fontSize = 30; + + if (text.Length == 1) + { + x += 11; + } + else if (text.Length == 2) + { + x += 3; + } + else if (text.Length == 3) + { + //x += 1; + y += 3; + fontSize = 24; + } + + using (var font = new Font("Sans-Serif", fontSize, FontStyle.Regular, GraphicsUnit.Pixel)) + { + using (var fontBrush = new SolidBrush(Color.White)) + { + graphics.DrawString(text, font, fontBrush, x, y); + } + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 5268faa4fe..98a87d03db 100644 --- a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using System; @@ -30,6 +31,13 @@ namespace MediaBrowser.Server.Implementations.Library }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); + private readonly IFileSystem _fileSystem; + + public CoreResolutionIgnoreRule(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + /// /// Shoulds the ignore. /// @@ -60,23 +68,12 @@ namespace MediaBrowser.Server.Implementations.Library return false; } - // Drives will sometimes be hidden - if (args.Path.EndsWith(Path.VolumeSeparatorChar + "\\", StringComparison.OrdinalIgnoreCase)) + // Sometimes these are marked hidden + if (_fileSystem.IsRootPath(args.Path)) { return false; } - // Shares will sometimes be hidden - if (args.Path.StartsWith("\\", StringComparison.OrdinalIgnoreCase)) - { - // Look for a share, e.g. \\server\movies - // Is there a better way to detect if a path is a share without using native code? - if (args.Path.Substring(2).Split(Path.DirectorySeparatorChar).Length == 2) - { - return false; - } - } - return true; } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 1a52bb2248..8e4e71fd42 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -499,21 +499,18 @@ namespace MediaBrowser.Server.Implementations.Library // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - args.FileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); + var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); // Need to remove subpaths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action if (isPhysicalRoot) { - var paths = args.FileSystemDictionary.Keys.ToList(); + var paths = NormalizeRootPathList(fileSystemDictionary.Keys); - foreach (var subPath in paths - .Where(subPath => !subPath.EndsWith(":\\", StringComparison.OrdinalIgnoreCase) && paths.Any(i => subPath.StartsWith(i.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))) - { - _logger.Info("Ignoring duplicate path: {0}", subPath); - args.FileSystemDictionary.Remove(subPath); - } + fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName); } + + args.FileSystemDictionary = fileSystemDictionary; } // Check to see if we should resolve based on our contents @@ -525,6 +522,23 @@ namespace MediaBrowser.Server.Implementations.Library return ResolveItem(args); } + public IEnumerable NormalizeRootPathList(IEnumerable paths) + { + var list = paths.Select(_fileSystem.NormalizePath) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var dupes = list.Where(subPath => !subPath.EndsWith(":\\", StringComparison.OrdinalIgnoreCase) && list.Any(i => _fileSystem.ContainsSubPath(i, subPath))) + .ToList(); + + foreach (var dupe in dupes) + { + _logger.Info("Found duplicate path: {0}", dupe); + } + + return list.Except(dupes, StringComparer.OrdinalIgnoreCase); + } + /// /// Determines whether a path should be ignored based on its contents - called after the contents have been read /// diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 0b2d0c5e90..5857088d2a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -4,11 +4,13 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -21,13 +23,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly IUserDataManager _userDataManager; private readonly IDtoService _dtoService; + private readonly IItemRepository _itemRepo; - public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger) + public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger, IItemRepository itemRepo) { _dtoService = dtoService; _userDataManager = userDataManager; _imageProcessor = imageProcessor; _logger = logger; + _itemRepo = itemRepo; } public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service, LiveTvProgram program, LiveTvChannel channel) @@ -180,7 +184,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return status.ToString(); } - public RecordingInfoDto GetRecordingInfoDto(LiveTvRecording recording, LiveTvChannel channel, ILiveTvService service, User user = null) + public RecordingInfoDto GetRecordingInfoDto(ILiveTvRecording recording, LiveTvChannel channel, ILiveTvService service, User user = null) { var info = recording.RecordingInfo; @@ -216,7 +220,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv IsNews = info.IsNews, IsKids = info.IsKids, IsPremiere = info.IsPremiere, - RunTimeTicks = (info.EndDate - info.StartDate).Ticks + RunTimeTicks = (info.EndDate - info.StartDate).Ticks, + LocationType = recording.LocationType, + + MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery + { + ItemId = recording.Id + + }).ToList() }; var imageTag = GetImageTag(recording); @@ -330,7 +341,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return dto; } - private Guid? GetImageTag(BaseItem info) + private Guid? GetImageTag(IHasImages info) { var path = info.PrimaryImagePath; @@ -351,39 +362,41 @@ namespace MediaBrowser.Server.Implementations.LiveTv return null; } + private const string InternalVersionNumber = "2"; + public Guid GetInternalChannelId(string serviceName, string externalId) { - var name = serviceName + externalId; + var name = serviceName + externalId + InternalVersionNumber; return name.ToLower().GetMBId(typeof(LiveTvChannel)); } public Guid GetInternalTimerId(string serviceName, string externalId) { - var name = serviceName + externalId; + var name = serviceName + externalId + InternalVersionNumber; return name.ToLower().GetMD5(); } public Guid GetInternalSeriesTimerId(string serviceName, string externalId) { - var name = serviceName + externalId; + var name = serviceName + externalId + InternalVersionNumber; return name.ToLower().GetMD5(); } public Guid GetInternalProgramId(string serviceName, string externalId) { - var name = serviceName + externalId; + var name = serviceName + externalId + InternalVersionNumber; - return name.ToLower().GetMD5(); + return name.ToLower().GetMBId(typeof(LiveTvProgram)); } public Guid GetInternalRecordingId(string serviceName, string externalId) { - var name = serviceName + externalId; + var name = serviceName + externalId + InternalVersionNumber; - return name.ToLower().GetMD5(); + return name.ToLower().GetMBId(typeof(ILiveTvRecording)); } public async Task GetTimerInfo(TimerInfoDto dto, bool isNew, ILiveTvManager liveTv, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 218c930df8..c6e5a315d9 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -6,8 +6,8 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; @@ -31,7 +31,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly IItemRepository _itemRepo; private readonly IUserManager _userManager; - private readonly ILocalizationManager _localization; private readonly LiveTvDtoService _tvDtoService; private readonly List _services = new List(); @@ -39,16 +38,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv private Dictionary _channels = new Dictionary(); private Dictionary _programs = new Dictionary(); - public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager) + public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager) { _appPaths = appPaths; _fileSystem = fileSystem; _logger = logger; _itemRepo = itemRepo; - _localization = localization; _userManager = userManager; - _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger); + _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, _itemRepo); } /// @@ -82,7 +80,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (user != null) { channels = channels - .Where(i => i.IsParentalAllowed(user, _localization)) + .Where(i => i.IsParentalAllowed(user)) .OrderBy(i => { double number = 0; @@ -144,7 +142,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return obj; } - public async Task GetInternalRecording(string id, CancellationToken cancellationToken) + public async Task GetInternalRecording(string id, CancellationToken cancellationToken) { var service = ActiveService; @@ -255,23 +253,46 @@ namespace MediaBrowser.Server.Implementations.LiveTv return item; } - private async Task GetRecording(RecordingInfo info, string serviceName, CancellationToken cancellationToken) + private async Task GetRecording(RecordingInfo info, string serviceName, CancellationToken cancellationToken) { var isNew = false; var id = _tvDtoService.GetInternalRecordingId(serviceName, info.Id); - var item = _itemRepo.RetrieveItem(id) as LiveTvRecording; + var item = _itemRepo.RetrieveItem(id) as ILiveTvRecording; if (item == null) { - item = new LiveTvRecording + if (info.ChannelType == ChannelType.TV) { - Name = info.Name, - Id = id, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow - }; + item = new LiveTvVideoRecording + { + Name = info.Name, + Id = id, + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow, + VideoType = VideoType.VideoFile + }; + } + else + { + item = new LiveTvAudioRecording + { + Name = info.Name, + Id = id, + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow + }; + } + + if (!string.IsNullOrEmpty(info.Path)) + { + item.Path = info.Path; + } + else if (!string.IsNullOrEmpty(info.Url)) + { + item.Path = info.Url; + } isNew = true; } @@ -331,7 +352,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (user != null) { - programs = programs.Where(i => i.IsParentalAllowed(user, _localization)); + programs = programs.Where(i => i.IsParentalAllowed(user)); } var returnArray = programs @@ -450,10 +471,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId)); - var list = new List(); - var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); - list.AddRange(recordings); if (!string.IsNullOrEmpty(query.ChannelId)) { @@ -461,9 +479,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var currentServiceName = service.Name; - list = list - .Where(i => _tvDtoService.GetInternalChannelId(currentServiceName, i.ChannelId) == guid) - .ToList(); + recordings = recordings + .Where(i => _tvDtoService.GetInternalChannelId(currentServiceName, i.ChannelId) == guid); } if (!string.IsNullOrEmpty(query.Id)) @@ -472,27 +489,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv var currentServiceName = service.Name; - list = list - .Where(i => _tvDtoService.GetInternalRecordingId(currentServiceName, i.Id) == guid) - .ToList(); + recordings = recordings + .Where(i => _tvDtoService.GetInternalRecordingId(currentServiceName, i.Id) == guid); } if (!string.IsNullOrEmpty(query.GroupId)) { var guid = new Guid(query.GroupId); - list = list.Where(i => GetRecordingGroupIds(i).Contains(guid)) - .ToList(); + recordings = recordings.Where(i => GetRecordingGroupIds(i).Contains(guid)); + } + + if (query.IsRecording.HasValue) + { + var val = query.IsRecording.Value; + recordings = recordings.Where(i => (i.Status == RecordingStatus.InProgress) == val); } - IEnumerable entities = await GetEntities(list, service.Name, cancellationToken).ConfigureAwait(false); + IEnumerable entities = await GetEntities(recordings, service.Name, cancellationToken).ConfigureAwait(false); entities = entities.OrderByDescending(i => i.RecordingInfo.StartDate); if (user != null) { var currentUser = user; - entities = entities.Where(i => i.IsParentalAllowed(currentUser, _localization)); + entities = entities.Where(i => i.IsParentalAllowed(currentUser)); } if (query.StartIndex.HasValue) @@ -520,7 +541,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv }; } - private Task GetEntities(IEnumerable recordings, string serviceName, CancellationToken cancellationToken) + private Task GetEntities(IEnumerable recordings, string serviceName, CancellationToken cancellationToken) { var tasks = recordings.Select(i => GetRecording(i, serviceName, cancellationToken)); diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs index 0b5ec285e4..9f6ab85a40 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public override bool Supports(BaseItem item) { - return item is LiveTvRecording; + return item is ILiveTvRecording; } protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) @@ -55,7 +55,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv try { - changed = await DownloadImage((LiveTvRecording)item, cancellationToken).ConfigureAwait(false); + changed = await DownloadImage((ILiveTvRecording)item, cancellationToken).ConfigureAwait(false); } catch (HttpException ex) { @@ -74,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return changed; } - private async Task DownloadImage(LiveTvRecording item, CancellationToken cancellationToken) + private async Task DownloadImage(ILiveTvRecording item, CancellationToken cancellationToken) { var recordingInfo = item.RecordingInfo; @@ -133,7 +133,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv // Dummy up the original url var url = item.ServiceName + recordingInfo.Id; - await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); + await _providerManager.SaveImage((BaseItem)item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); return true; } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 86dd0bc75a..0efd3c236a 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -100,7 +100,8 @@ - + + diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index 0346aba977..ec797b6889 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -73,7 +73,7 @@ namespace MediaBrowser.Server.Implementations.Providers throw new ArgumentNullException("mimeType"); } - var saveLocally = _config.Configuration.SaveLocalMeta && item.Parent != null && !(item is Audio); + var saveLocally = item.IsSaveLocalMetadataEnabled() && item.Parent != null && !(item is Audio); if (item is IItemByName || item is User) { diff --git a/MediaBrowser.Server.Mono/Networking/NetworkManager.cs b/MediaBrowser.Server.Mono/Networking/NetworkManager.cs index 174d061bf6..a1cdf87e47 100644 --- a/MediaBrowser.Server.Mono/Networking/NetworkManager.cs +++ b/MediaBrowser.Server.Mono/Networking/NetworkManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Implementations.Networking; using MediaBrowser.Common.Net; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using System; using System.Collections.Generic; @@ -26,9 +27,9 @@ namespace MediaBrowser.ServerApplication.Networking /// Gets a list of network devices /// /// PC's in the Domain - public IEnumerable GetNetworkDevices() + public IEnumerable GetNetworkDevices() { - return new List (); + return new List (); } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 514fbe6c1b..fc13191368 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -272,7 +272,7 @@ namespace MediaBrowser.ServerApplication DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor); RegisterSingleInstance(DtoService); - LiveTvManager = new LiveTvManager(ApplicationPaths, FileSystemManager, Logger, ItemRepository, ImageProcessor, LocalizationManager, UserDataManager, DtoService, UserManager); + LiveTvManager = new LiveTvManager(ApplicationPaths, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager); RegisterSingleInstance(LiveTvManager); progress.Report(15); diff --git a/MediaBrowser.ServerApplication/Networking/NetworkManager.cs b/MediaBrowser.ServerApplication/Networking/NetworkManager.cs index 4799dcc723..e80c8ff3f4 100644 --- a/MediaBrowser.ServerApplication/Networking/NetworkManager.cs +++ b/MediaBrowser.ServerApplication/Networking/NetworkManager.cs @@ -1,5 +1,8 @@ -using MediaBrowser.Common.Implementations.Networking; +using System.Globalization; +using System.IO; +using MediaBrowser.Common.Implementations.Networking; using MediaBrowser.Common.Net; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using System; using System.Collections.Generic; @@ -79,7 +82,7 @@ namespace MediaBrowser.ServerApplication.Networking /// /// Arraylist that represents all the SV_TYPE_WORKSTATION and SV_TYPE_SERVER /// PC's in the Domain - public IEnumerable GetNetworkDevices() + private IEnumerable GetNetworkDevicesInternal() { //local fields const int MAX_PREFERRED_LENGTH = -1; @@ -131,6 +134,33 @@ namespace MediaBrowser.ServerApplication.Networking NativeMethods.NetApiBufferFree(buffer); } } + + /// + /// Gets available devices within the domain + /// + /// PC's in the Domain + public IEnumerable GetNetworkDevices() + { + return GetNetworkDevicesInternal().Select(c => new FileSystemEntryInfo + { + Name = c, + Path = NetworkPrefix + c, + Type = FileSystemEntryType.NetworkComputer + }); + } + + /// + /// Gets the network prefix. + /// + /// The network prefix. + private string NetworkPrefix + { + get + { + var separator = Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture); + return separator + separator; + } + } } } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 7743cc5278..9d8fb5675b 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -498,6 +498,7 @@ namespace MediaBrowser.WebDashboard.Api "livetvnewrecording.js", "livetvprogram.js", "livetvrecording.js", + "livetvrecordinglist.js", "livetvrecordings.js", "livetvtimer.js", "livetvseriestimer.js", diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 274be16b9f..592cf2bad4 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -872,6 +872,47 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }); }; + /** + * Gets shares from a network device + */ + self.getNetworkShares = function (path) { + + if (!path) { + throw new Error("null path"); + } + + var options = {}; + options.path = path; + + var url = self.getUrl("Environment/NetworkShares", options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + /** + * Gets the parent of a given path + */ + self.getParentPath = function (path) { + + if (!path) { + throw new Error("null path"); + } + + var options = {}; + options.path = path; + + var url = self.getUrl("Environment/ParentPath", options); + + return self.ajax({ + type: "GET", + url: url + }); + }; + /** * Gets a list of physical drives from the server */ diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index e11c8390f1..e650e48a89 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -154,6 +154,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -424,6 +427,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 4cc0c4d551..7c295df9db 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file