pull/702/head
hatharry 8 years ago
commit cabf2cdc1b

@ -33,7 +33,9 @@ namespace Emby.Drawing.GDI
graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingMode = CompositingMode.SourceCopy;
// SourceCopy causes the image to be blank in OSX
//graphics.CompositingMode = CompositingMode.SourceCopy;
for (var row = 0; row < rows; row++) for (var row = 0; row < rows; row++)
{ {
@ -44,19 +46,9 @@ namespace Emby.Drawing.GDI
if (files.Count > index) if (files.Count > index)
{ {
using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true)) using (var imgtemp = Image.FromFile(files[index]))
{ {
using (var memoryStream = new MemoryStream()) graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
{
fileStream.CopyTo(memoryStream);
memoryStream.Position = 0;
using (var imgtemp = Image.FromStream(memoryStream, true, false))
{
graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
}
}
} }
} }
@ -90,7 +82,9 @@ namespace Emby.Drawing.GDI
graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingMode = CompositingMode.SourceCopy;
// SourceCopy causes the image to be blank in OSX
//graphics.CompositingMode = CompositingMode.SourceCopy;
for (var row = 0; row < rows; row++) for (var row = 0; row < rows; row++)
{ {
@ -99,21 +93,10 @@ namespace Emby.Drawing.GDI
var x = col * singleSize; var x = col * singleSize;
var y = row * singleSize; var y = row * singleSize;
using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true)) using (var imgtemp = Image.FromFile(files[index]))
{ {
using (var memoryStream = new MemoryStream()) graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
{
fileStream.CopyTo(memoryStream);
memoryStream.Position = 0;
using (var imgtemp = Image.FromStream(memoryStream, true, false))
{
graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
}
}
} }
index++; index++;
} }
} }
@ -121,16 +104,5 @@ namespace Emby.Drawing.GDI
} }
} }
} }
private static Stream GetStream(Image image)
{
var ms = new MemoryStream();
image.Save(ms, ImageFormat.Png);
ms.Position = 0;
return ms;
}
} }
} }

@ -119,9 +119,11 @@ namespace Emby.Drawing.GDI
thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality; thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic; thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality; thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
thumbnailGraph.CompositingMode = !hasPostProcessing ?
CompositingMode.SourceCopy : // SourceCopy causes the image to be blank in OSX
CompositingMode.SourceOver; //thumbnailGraph.CompositingMode = !hasPostProcessing ?
// CompositingMode.SourceCopy :
// CompositingMode.SourceOver;
SetBackgroundColor(thumbnailGraph, options); SetBackgroundColor(thumbnailGraph, options);

@ -63,6 +63,15 @@ namespace MediaBrowser.Api
Instance = this; Instance = this;
_sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
_sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
}
private void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
{
PingTranscodingJob(e.PlaySessionId, e.IsPaused);
}
} }
void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e) void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
@ -126,9 +135,10 @@ namespace MediaBrowser.Api
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose) protected virtual void Dispose(bool dispose)
{ {
var jobCount = _activeTranscodingJobs.Count; var list = _activeTranscodingJobs.ToList();
var jobCount = list.Count;
Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, false, path => true)); Parallel.ForEach(list, j => KillTranscodingJob(j, false, path => true));
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
if (jobCount > 0) if (jobCount > 0)
@ -182,13 +192,13 @@ namespace MediaBrowser.Api
_activeTranscodingJobs.Add(job); _activeTranscodingJobs.Add(job);
ReportTranscodingProgress(job, state, null, null, null, null); ReportTranscodingProgress(job, state, null, null, null, null, null);
return job; return job;
} }
} }
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded) public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{ {
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
@ -198,6 +208,7 @@ namespace MediaBrowser.Api
job.CompletionPercentage = percentComplete; job.CompletionPercentage = percentComplete;
job.TranscodingPositionTicks = ticks; job.TranscodingPositionTicks = ticks;
job.BytesTranscoded = bytesTranscoded; job.BytesTranscoded = bytesTranscoded;
job.BitRate = bitRate;
} }
var deviceId = state.Request.DeviceId; var deviceId = state.Request.DeviceId;
@ -209,7 +220,7 @@ namespace MediaBrowser.Api
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
{ {
Bitrate = state.TotalOutputBitrate, Bitrate = bitRate ?? state.TotalOutputBitrate,
AudioCodec = audioCodec, AudioCodec = audioCodec,
VideoCodec = videoCodec, VideoCodec = videoCodec,
Container = state.OutputContainer, Container = state.OutputContainer,
@ -348,7 +359,7 @@ namespace MediaBrowser.Api
return; return;
} }
var timerDuration = 1000; var timerDuration = 10000;
if (job.Type != TranscodingJobType.Progressive) if (job.Type != TranscodingJobType.Progressive)
{ {
@ -400,7 +411,7 @@ namespace MediaBrowser.Api
} }
} }
Logger.Debug("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); Logger.Info("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
KillTranscodingJob(job, true, path => true); KillTranscodingJob(job, true, path => true);
} }
@ -558,13 +569,13 @@ namespace MediaBrowser.Api
{ {
} }
catch (IOException ex) catch (IOException)
{ {
//Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path); //Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
DeletePartialStreamFiles(path, jobType, retryCount + 1, 500); DeletePartialStreamFiles(path, jobType, retryCount + 1, 500);
} }
catch (Exception ex) catch
{ {
//Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path); //Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
} }
@ -684,6 +695,7 @@ namespace MediaBrowser.Api
public long? BytesDownloaded { get; set; } public long? BytesDownloaded { get; set; }
public long? BytesTranscoded { get; set; } public long? BytesTranscoded { get; set; }
public int? BitRate { get; set; }
public long? TranscodingPositionTicks { get; set; } public long? TranscodingPositionTicks { get; set; }
public long? DownloadPositionTicks { get; set; } public long? DownloadPositionTicks { get; set; }

@ -139,6 +139,10 @@ namespace MediaBrowser.Api
{ {
options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value; options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value;
} }
if (hasDtoOptions.EnableUserData.HasValue)
{
options.EnableUserData = hasDtoOptions.EnableUserData.Value;
}
if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes)) if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
{ {

@ -4,6 +4,7 @@ namespace MediaBrowser.Api
public interface IHasDtoOptions : IHasItemFields public interface IHasDtoOptions : IHasItemFields
{ {
bool? EnableImages { get; set; } bool? EnableImages { get; set; }
bool? EnableUserData { get; set; }
int? ImageTypeLimit { get; set; } int? ImageTypeLimit { get; set; }

@ -573,11 +573,9 @@ namespace MediaBrowser.Api.Images
var outputFormats = GetOutputFormats(request, imageInfo, cropwhitespace, supportedImageEnhancers); var outputFormats = GetOutputFormats(request, imageInfo, cropwhitespace, supportedImageEnhancers);
var cacheGuid = new Guid(_imageProcessor.GetImageCacheTag(item, imageInfo, supportedImageEnhancers));
TimeSpan? cacheDuration = null; TimeSpan? cacheDuration = null;
if (!string.IsNullOrEmpty(request.Tag) && cacheGuid == new Guid(request.Tag)) if (!string.IsNullOrEmpty(request.Tag))
{ {
cacheDuration = TimeSpan.FromDays(365); cacheDuration = TimeSpan.FromDays(365);
} }

@ -5,6 +5,7 @@ using MediaBrowser.Controller.Providers;
using ServiceStack; using ServiceStack;
using System.Threading; using System.Threading;
using CommonIO; using CommonIO;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Api namespace MediaBrowser.Api
{ {
@ -39,12 +40,14 @@ namespace MediaBrowser.Api
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
public ItemRefreshService(ILibraryManager libraryManager, IProviderManager providerManager, IFileSystem fileSystem) public ItemRefreshService(ILibraryManager libraryManager, IProviderManager providerManager, IFileSystem fileSystem, ILogger logger)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_providerManager = providerManager; _providerManager = providerManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_logger = logger;
} }
/// <summary> /// <summary>
@ -69,7 +72,7 @@ namespace MediaBrowser.Api
private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request) private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request)
{ {
return new MetadataRefreshOptions(new DirectoryService(_fileSystem)) return new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
{ {
MetadataRefreshMode = request.MetadataRefreshMode, MetadataRefreshMode = request.MetadataRefreshMode,
ImageRefreshMode = request.ImageRefreshMode, ImageRefreshMode = request.ImageRefreshMode,

@ -304,7 +304,7 @@ namespace MediaBrowser.Api
item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : (DateTime?)null; item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : (DateTime?)null;
item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : (DateTime?)null; item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : (DateTime?)null;
item.ProductionYear = request.ProductionYear; item.ProductionYear = request.ProductionYear;
item.OfficialRating = request.OfficialRating; item.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
item.CustomRating = request.CustomRating; item.CustomRating = request.CustomRating;
SetProductionLocations(item, request); SetProductionLocations(item, request);

@ -154,9 +154,12 @@ namespace MediaBrowser.Api.Library
public void Post(PerformOrganization request) public void Post(PerformOrganization request)
{ {
// Don't await this
var task = _iFileOrganizationService.PerformOrganization(request.Id); var task = _iFileOrganizationService.PerformOrganization(request.Id);
Task.WaitAll(task); // Async processing (close dialog early instead of waiting until the file has been copied)
// Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display
task.Wait(2000);
} }
public void Post(OrganizeEpisode request) public void Post(OrganizeEpisode request)
@ -168,6 +171,7 @@ namespace MediaBrowser.Api.Library
dicNewProviderIds = request.NewSeriesProviderIds; dicNewProviderIds = request.NewSeriesProviderIds;
} }
// Don't await this
var task = _iFileOrganizationService.PerformEpisodeOrganization(new EpisodeFileOrganizationRequest var task = _iFileOrganizationService.PerformEpisodeOrganization(new EpisodeFileOrganizationRequest
{ {
EndingEpisodeNumber = request.EndingEpisodeNumber, EndingEpisodeNumber = request.EndingEpisodeNumber,
@ -182,11 +186,9 @@ namespace MediaBrowser.Api.Library
TargetFolder = request.TargetFolder TargetFolder = request.TargetFolder
}); });
// For async processing (close dialog early instead of waiting until the file has been copied) // Async processing (close dialog early instead of waiting until the file has been copied)
//var tasks = new Task[] { task }; // Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display
//Task.WaitAll(tasks, 8000); task.Wait(2000);
Task.WaitAll(task);
} }
public object Get(GetSmartMatchInfos request) public object Get(GetSmartMatchInfos request)

@ -10,6 +10,9 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Api.Library namespace MediaBrowser.Api.Library
{ {
@ -52,6 +55,8 @@ namespace MediaBrowser.Api.Library
/// </summary> /// </summary>
/// <value>The path.</value> /// <value>The path.</value>
public string[] Paths { get; set; } public string[] Paths { get; set; }
public LibraryOptions LibraryOptions { get; set; }
} }
[Route("/Library/VirtualFolders", "DELETE")] [Route("/Library/VirtualFolders", "DELETE")]
@ -136,6 +141,14 @@ namespace MediaBrowser.Api.Library
public bool RefreshLibrary { get; set; } public bool RefreshLibrary { get; set; }
} }
[Route("/Library/VirtualFolders/LibraryOptions", "POST")]
public class UpdateLibraryOptions : IReturnVoid
{
public string Id { get; set; }
public LibraryOptions LibraryOptions { get; set; }
}
/// <summary> /// <summary>
/// Class LibraryStructureService /// Class LibraryStructureService
/// </summary> /// </summary>
@ -184,13 +197,22 @@ namespace MediaBrowser.Api.Library
return ToOptimizedSerializedResultUsingCache(result); return ToOptimizedSerializedResultUsingCache(result);
} }
public void Post(UpdateLibraryOptions request)
{
var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(request.Id);
collectionFolder.UpdateLibraryOptions(request.LibraryOptions);
}
/// <summary> /// <summary>
/// Posts the specified request. /// Posts the specified request.
/// </summary> /// </summary>
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
public void Post(AddVirtualFolder request) public void Post(AddVirtualFolder request)
{ {
_libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, request.RefreshLibrary); var libraryOptions = request.LibraryOptions ?? new LibraryOptions();
_libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, libraryOptions, request.RefreshLibrary);
} }
/// <summary> /// <summary>
@ -214,12 +236,12 @@ namespace MediaBrowser.Api.Library
var currentPath = Path.Combine(rootFolderPath, request.Name); var currentPath = Path.Combine(rootFolderPath, request.Name);
var newPath = Path.Combine(rootFolderPath, request.NewName); var newPath = Path.Combine(rootFolderPath, request.NewName);
if (!_fileSystem.DirectoryExists(currentPath)) if (!_fileSystem.DirectoryExists(currentPath))
{ {
throw new DirectoryNotFoundException("The media collection does not exist"); throw new DirectoryNotFoundException("The media collection does not exist");
} }
if (!string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase) && _fileSystem.DirectoryExists(newPath)) if (!string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase) && _fileSystem.DirectoryExists(newPath))
{ {
throw new ArgumentException("There is already a media collection with the name " + newPath + "."); throw new ArgumentException("There is already a media collection with the name " + newPath + ".");
} }
@ -234,11 +256,11 @@ namespace MediaBrowser.Api.Library
//Create an unique name //Create an unique name
var temporaryName = Guid.NewGuid().ToString(); var temporaryName = Guid.NewGuid().ToString();
var temporaryPath = Path.Combine(rootFolderPath, temporaryName); var temporaryPath = Path.Combine(rootFolderPath, temporaryName);
_fileSystem.MoveDirectory(currentPath, temporaryPath); _fileSystem.MoveDirectory(currentPath, temporaryPath);
currentPath = temporaryPath; currentPath = temporaryPath;
} }
_fileSystem.MoveDirectory(currentPath, newPath); _fileSystem.MoveDirectory(currentPath, newPath);
} }
finally finally
{ {

@ -82,6 +82,9 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "AddCurrentProgram", Description = "Optional. Adds current program info to each channel", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "AddCurrentProgram", Description = "Optional. Adds current program info to each channel", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public bool AddCurrentProgram { get; set; } public bool AddCurrentProgram { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
public GetChannels() public GetChannels()
{ {
AddCurrentProgram = true; AddCurrentProgram = true;
@ -149,6 +152,9 @@ namespace MediaBrowser.Api.LiveTv
public bool EnableTotalRecordCount { get; set; } public bool EnableTotalRecordCount { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
public GetRecordings() public GetRecordings()
{ {
EnableTotalRecordCount = true; EnableTotalRecordCount = true;
@ -271,6 +277,9 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; } public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
/// <summary> /// <summary>
/// Fields to return within the items, in addition to basic information /// Fields to return within the items, in addition to basic information
/// </summary> /// </summary>
@ -331,6 +340,9 @@ namespace MediaBrowser.Api.LiveTv
/// <value>The fields.</value> /// <value>The fields.</value>
[ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Fields { get; set; } public string Fields { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
} }
[Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")] [Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")]
@ -726,7 +738,12 @@ namespace MediaBrowser.Api.LiveTv
var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(request.UserId); var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(request.UserId);
var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, GetDtoOptions(Request), user).ConfigureAwait(false)).ToArray(); var options = GetDtoOptions(request);
RemoveFields(options);
options.AddCurrentProgram = request.AddCurrentProgram;
var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, options, user).ConfigureAwait(false)).ToArray();
var result = new QueryResult<BaseItemDto> var result = new QueryResult<BaseItemDto>
{ {
@ -737,6 +754,14 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedSerializedResultUsingCache(result); return ToOptimizedSerializedResultUsingCache(result);
} }
private void RemoveFields(DtoOptions options)
{
options.Fields.Remove(ItemFields.CanDelete);
options.Fields.Remove(ItemFields.CanDownload);
options.Fields.Remove(ItemFields.DisplayPreferencesId);
options.Fields.Remove(ItemFields.Etag);
}
public object Get(GetChannel request) public object Get(GetChannel request)
{ {
var user = string.IsNullOrWhiteSpace(request.UserId) ? null : _userManager.GetUserById(request.UserId); var user = string.IsNullOrWhiteSpace(request.UserId) ? null : _userManager.GetUserById(request.UserId);
@ -997,10 +1022,7 @@ namespace MediaBrowser.Api.LiveTv
public async Task<object> Get(GetRecordingGroup request) public async Task<object> Get(GetRecordingGroup request)
{ {
var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery(), CancellationToken.None).ConfigureAwait(false);
{
}, CancellationToken.None).ConfigureAwait(false);
var group = result.Items.FirstOrDefault(i => string.Equals(i.Id, request.Id, StringComparison.OrdinalIgnoreCase)); var group = result.Items.FirstOrDefault(i => string.Equals(i.Id, request.Id, StringComparison.OrdinalIgnoreCase));

@ -192,7 +192,8 @@ namespace MediaBrowser.Api.Movies
SortOrder = SortOrder.Descending, SortOrder = SortOrder.Descending,
Limit = 7, Limit = 7,
ParentId = parentIdGuid, ParentId = parentIdGuid,
Recursive = true Recursive = true,
IsPlayed = true
}; };
var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList(); var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();

@ -22,6 +22,8 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
namespace MediaBrowser.Api.Playback namespace MediaBrowser.Api.Playback
{ {
@ -69,6 +71,9 @@ namespace MediaBrowser.Api.Playback
protected IZipClient ZipClient { get; private set; } protected IZipClient ZipClient { get; private set; }
protected IJsonSerializer JsonSerializer { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; }
public static IServerApplicationHost AppHost;
public static IHttpClient HttpClient;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class. /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary> /// </summary>
@ -286,28 +291,46 @@ namespace MediaBrowser.Api.Playback
protected string GetH264Encoder(StreamState state) protected string GetH264Encoder(StreamState state)
{ {
var defaultEncoder = "libx264";
// Only use alternative encoders for video files. // Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
if (state.VideoType == VideoType.VideoFile) if (state.VideoType == VideoType.VideoFile)
{ {
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) || var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase)) var hwType = encodingOptions.HardwareAccelerationType;
if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{ {
return "h264_qsv"; return GetAvailableEncoder("h264_qsv", defaultEncoder);
} }
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
{
return GetAvailableEncoder("h264_nvenc", defaultEncoder);
}
if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase))
{ {
return "h264_nvenc"; return GetAvailableEncoder("h264_omx", defaultEncoder);
} }
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase)) if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice))
{ {
return "h264_omx"; return GetAvailableEncoder("h264_vaapi", defaultEncoder);
} }
} }
return "libx264"; return defaultEncoder;
}
private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder)
{
if (MediaEncoder.SupportsEncoder(preferredEncoder))
{
return preferredEncoder;
}
return defaultEncoder;
} }
/// <summary> /// <summary>
@ -409,7 +432,8 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrEmpty(state.VideoRequest.Profile)) if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
{ {
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase)) if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{ {
// not supported by h264_omx // not supported by h264_omx
param += " -profile:v " + state.VideoRequest.Profile; param += " -profile:v " + state.VideoRequest.Profile;
@ -464,7 +488,8 @@ namespace MediaBrowser.Api.Playback
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) && if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{ {
param = "-pix_fmt yuv420p " + param; param = "-pix_fmt yuv420p " + param;
} }
@ -530,59 +555,97 @@ namespace MediaBrowser.Api.Playback
var filters = new List<string>(); var filters = new List<string>();
if (state.DeInterlace) if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
}
else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{ {
filters.Add("yadif=0:-1:0"); filters.Add("yadif=0:-1:0");
} }
// If fixed dimensions were supplied if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
if (request.Width.HasValue && request.Height.HasValue)
{ {
var widthParam = request.Width.Value.ToString(UsCulture); // Work around vaapi's reduced scaling features
var heightParam = request.Height.Value.ToString(UsCulture); var scaler = "scale_vaapi";
filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam)); // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
} // (outputWidth, outputHeight). The user may request precise output dimensions or maximum
// output dimensions. Output dimensions are guaranteed to be even.
decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width);
decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height);
decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth;
decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight;
decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth;
decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight;
// If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size if (outputWidth > maximumWidth || outputHeight > maximumHeight)
else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue) {
{ var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture); outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture); outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
}
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); outputWidth = 2 * Math.Truncate(outputWidth / 2);
} outputHeight = 2 * Math.Truncate(outputHeight / 2);
// If a fixed width was requested if (outputWidth != inputWidth || outputHeight != inputHeight)
else if (request.Width.HasValue) {
filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(UsCulture), outputHeight.ToString(UsCulture)));
}
}
else
{ {
var widthParam = request.Width.Value.ToString(UsCulture); // If fixed dimensions were supplied
if (request.Width.HasValue && request.Height.HasValue)
{
var widthParam = request.Width.Value.ToString(UsCulture);
var heightParam = request.Height.Value.ToString(UsCulture);
filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam)); filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
} }
// If a fixed height was requested // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
else if (request.Height.HasValue) else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
{ {
var heightParam = request.Height.Value.ToString(UsCulture); var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam)); filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
} }
// If a max width was requested // If a fixed width was requested
else if (request.MaxWidth.HasValue) else if (request.Width.HasValue)
{ {
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture); var widthParam = request.Width.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam)); filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
} }
// If a max height was requested // If a fixed height was requested
else if (request.MaxHeight.HasValue) else if (request.Height.HasValue)
{ {
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture); var heightParam = request.Height.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
}
// If a max width was requested
else if (request.MaxWidth.HasValue)
{
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
}
// If a max height was requested
else if (request.MaxHeight.HasValue)
{
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam)); filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam));
}
} }
var output = string.Empty; var output = string.Empty;
@ -917,6 +980,15 @@ namespace MediaBrowser.Api.Playback
} }
} }
if (state.VideoRequest != null)
{
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
{
arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
}
}
return arg.Trim(); return arg.Trim();
} }
@ -1042,14 +1114,14 @@ namespace MediaBrowser.Api.Playback
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
Logger.Info(commandLineLogMessage); Logger.Info(commandLineLogMessage);
var logFilePrefix = "transcode"; var logFilePrefix = "ffmpeg-transcode";
if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
logFilePrefix = "directstream"; logFilePrefix = "ffmpeg-directstream";
} }
else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
logFilePrefix = "remux"; logFilePrefix = "ffmpeg-remux";
} }
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt"); var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
@ -1080,7 +1152,7 @@ namespace MediaBrowser.Api.Playback
//process.BeginOutputReadLine(); //process.BeginOutputReadLine();
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream)); var task = Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
// Wait for the file to exist before proceeeding // Wait for the file to exist before proceeeding
while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited) while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
@ -1099,28 +1171,30 @@ namespace MediaBrowser.Api.Playback
} }
StartThrottler(state, transcodingJob); StartThrottler(state, transcodingJob);
ReportUsage(state);
return transcodingJob; return transcodingJob;
} }
private void StartThrottler(StreamState state, TranscodingJob transcodingJob) private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
{ {
if (EnableThrottling(state) && state.InputProtocol == MediaProtocol.File && if (EnableThrottling(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
state.RunTimeTicks.HasValue &&
state.VideoType == VideoType.VideoFile &&
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo) transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
{ state.TranscodingThrottler.Start();
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
state.TranscodingThrottler.Start();
}
} }
} }
protected virtual bool EnableThrottling(StreamState state) protected virtual bool EnableThrottling(StreamState state)
{ {
return true; // do not use throttling with hardware encoders
return state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue &&
state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
state.IsInputVideo &&
state.VideoType == VideoType.VideoFile &&
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) &&
string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
} }
private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target) private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
@ -1158,6 +1232,7 @@ namespace MediaBrowser.Api.Playback
double? percent = null; double? percent = null;
TimeSpan? transcodingPosition = null; TimeSpan? transcodingPosition = null;
long? bytesTranscoded = null; long? bytesTranscoded = null;
int? bitRate = null;
var parts = line.Split(' '); var parts = line.Split(' ');
@ -1221,11 +1296,32 @@ namespace MediaBrowser.Api.Playback
} }
} }
} }
else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
{
var rate = part.Split(new[] { '=' }, 2).Last();
int? scale = null;
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
{
scale = 1024;
rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
}
if (scale.HasValue)
{
float val;
if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
{
bitRate = (int)Math.Ceiling(val * scale.Value);
}
}
}
} }
if (framerate.HasValue || percent.HasValue) if (framerate.HasValue || percent.HasValue)
{ {
ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded); ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
} }
} }
@ -1547,13 +1643,6 @@ namespace MediaBrowser.Api.Playback
} }
} }
else if (i == 25) else if (i == 25)
{
if (videoRequest != null)
{
videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
}
else if (i == 26)
{ {
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null) if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
{ {
@ -1564,17 +1653,21 @@ namespace MediaBrowser.Api.Playback
} }
} }
} }
else if (i == 27) else if (i == 26)
{ {
request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture); request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
} }
else if (i == 28) else if (i == 27)
{ {
if (videoRequest != null) if (videoRequest != null)
{ {
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
} }
} }
else if (i == 28)
{
request.Tag = val;
}
} }
} }
@ -1788,6 +1881,19 @@ namespace MediaBrowser.Api.Playback
{ {
state.OutputAudioCodec = "copy"; state.OutputAudioCodec = "copy";
} }
else
{
// If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
if (!string.IsNullOrWhiteSpace(auth.UserId))
{
var user = UserManager.GetUserById(auth.UserId);
if (!user.Policy.EnableAudioPlaybackTranscoding)
{
state.OutputAudioCodec = "copy";
}
}
}
} }
private void AttachMediaSourceInfo(StreamState state, private void AttachMediaSourceInfo(StreamState state,
@ -2159,13 +2265,127 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null) if (state.VideoRequest != null)
{ {
state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps; state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
state.VideoRequest.ForceLiveStream = transcodingProfile.ForceLiveStream;
state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
} }
} }
} }
} }
private async void ReportUsage(StreamState state)
{
try
{
await ReportUsageInternal(state).ConfigureAwait(false);
}
catch
{
}
}
private Task ReportUsageInternal(StreamState state)
{
if (!ServerConfigurationManager.Configuration.EnableAnonymousUsageReporting)
{
return Task.FromResult(true);
}
if (!MediaEncoder.IsDefaultEncoderPath)
{
return Task.FromResult(true);
}
var dict = new Dictionary<string, string>();
var outputAudio = GetAudioEncoder(state);
if (!string.IsNullOrWhiteSpace(outputAudio))
{
dict["outputAudio"] = outputAudio;
}
var outputVideo = GetVideoEncoder(state);
if (!string.IsNullOrWhiteSpace(outputVideo))
{
dict["outputVideo"] = outputVideo;
}
if (ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
return Task.FromResult(true);
}
dict["id"] = AppHost.SystemId;
dict["type"] = state.VideoRequest == null ? "Audio" : "Video";
var audioStream = state.AudioStream;
if (audioStream != null && !string.IsNullOrWhiteSpace(audioStream.Codec))
{
dict["inputAudio"] = audioStream.Codec;
}
var videoStream = state.VideoStream;
if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec))
{
dict["inputVideo"] = videoStream.Codec;
}
var cert = GetType().Assembly.GetModules().First().GetSignerCertificate();
if (cert != null)
{
dict["assemblySig"] = cert.GetCertHashString();
dict["certSubject"] = cert.Subject ?? string.Empty;
dict["certIssuer"] = cert.Issuer ?? string.Empty;
}
else
{
return Task.FromResult(true);
}
if (state.SupportedAudioCodecs.Count > 0)
{
dict["supportedAudioCodecs"] = string.Join(",", state.SupportedAudioCodecs.ToArray());
}
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
dict["appName"] = auth.Client ?? string.Empty;
dict["appVersion"] = auth.Version ?? string.Empty;
dict["device"] = auth.Device ?? string.Empty;
dict["deviceId"] = auth.DeviceId ?? string.Empty;
dict["context"] = "streaming";
//Logger.Info(JsonSerializer.SerializeToString(dict));
if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
list.Add(outputAudio);
ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
}
if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
list.Add(outputVideo);
ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
}
ServerConfigurationManager.SaveConfiguration();
//Logger.Info(JsonSerializer.SerializeToString(dict));
var options = new HttpRequestOptions()
{
Url = "https://mb3admin.com/admin/service/transcoding/report",
CancellationToken = CancellationToken.None,
LogRequest = false,
LogErrors = false
};
options.RequestContent = JsonSerializer.SerializeToString(dict);
options.RequestContentType = "application/json";
return HttpClient.Post(options);
}
/// <summary> /// <summary>
/// Adds the dlna headers. /// Adds the dlna headers.
/// </summary> /// </summary>

@ -3,7 +3,6 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
@ -282,11 +281,6 @@ namespace MediaBrowser.Api.Playback.Hls
{ {
var isLiveStream = (state.RunTimeTicks ?? 0) == 0; var isLiveStream = (state.RunTimeTicks ?? 0) == 0;
if (state.VideoRequest.ForceLiveStream)
{
return true;
}
return isLiveStream; return isLiveStream;
} }
} }

@ -257,8 +257,7 @@ namespace MediaBrowser.Api.Playback.Hls
return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
} }
// 256k private const int BufferSize = 81920;
private const int BufferSize = 262144;
private long GetStartPositionTicks(StreamState state, int requestedIndex) private long GetStartPositionTicks(StreamState state, int requestedIndex)
{ {

@ -284,6 +284,13 @@ namespace MediaBrowser.Api.Playback
options.ForceDirectPlay = true; options.ForceDirectPlay = true;
} }
} }
else if (item is Video)
{
if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
{
options.ForceDirectPlay = true;
}
}
// The MediaSource supports direct stream, now test to see if the client supports it // The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
@ -315,6 +322,13 @@ namespace MediaBrowser.Api.Playback
options.ForceDirectStream = true; options.ForceDirectStream = true;
} }
} }
else if (item is Video)
{
if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
{
options.ForceDirectStream = true;
}
}
// The MediaSource supports direct stream, now test to see if the client supports it // The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?

@ -142,7 +142,8 @@ namespace MediaBrowser.Api.Playback.Progressive
var outputPath = state.OutputFilePath; var outputPath = state.OutputFilePath;
var outputPathExists = FileSystem.FileExists(outputPath); var outputPathExists = FileSystem.FileExists(outputPath);
var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive); var transcodingJob = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
var isTranscodeCached = outputPathExists && transcodingJob != null;
AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached); AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached);
@ -153,42 +154,64 @@ namespace MediaBrowser.Api.Playback.Progressive
using (state) using (state)
{ {
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions TimeSpan? cacheDuration = null;
{
ResponseHeaders = responseHeaders,
ContentType = contentType,
IsHeadRequest = isHeadRequest,
Path = state.MediaPath
}).ConfigureAwait(false);
}
}
// Not static but transcode cache file exists if (!string.IsNullOrEmpty(request.Tag))
if (isTranscodeCached) {
{ cacheDuration = TimeSpan.FromDays(365);
var contentType = state.GetMimeType(outputPath); }
try
{
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{ {
ResponseHeaders = responseHeaders, ResponseHeaders = responseHeaders,
ContentType = contentType, ContentType = contentType,
IsHeadRequest = isHeadRequest, IsHeadRequest = isHeadRequest,
Path = outputPath Path = state.MediaPath,
CacheDuration = cacheDuration
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
finally
{
state.Dispose();
}
} }
//// Not static but transcode cache file exists
//if (isTranscodeCached && state.VideoRequest == null)
//{
// var contentType = state.GetMimeType(outputPath);
// try
// {
// if (transcodingJob != null)
// {
// ApiEntryPoint.Instance.OnTranscodeBeginRequest(transcodingJob);
// }
// return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
// {
// ResponseHeaders = responseHeaders,
// ContentType = contentType,
// IsHeadRequest = isHeadRequest,
// Path = outputPath,
// FileShare = FileShare.ReadWrite,
// OnComplete = () =>
// {
// if (transcodingJob != null)
// {
// ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
// }
// }
// }).ConfigureAwait(false);
// }
// finally
// {
// state.Dispose();
// }
//}
// Need to start ffmpeg // Need to start ffmpeg
try try
{ {
return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource) return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
.ConfigureAwait(false);
} }
catch catch
{ {
@ -347,9 +370,9 @@ namespace MediaBrowser.Api.Playback.Progressive
outputHeaders[item.Key] = item.Value; outputHeaders[item.Key] = item.Value;
} }
Func<Stream,Task> streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream, CancellationToken.None); var streamSource = new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, CancellationToken.None);
return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders); return ResultFactory.GetAsyncStreamWriter(streamSource);
} }
finally finally
{ {
@ -368,7 +391,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (totalBitrate > 0 && state.RunTimeTicks.HasValue) if (totalBitrate > 0 && state.RunTimeTicks.HasValue)
{ {
return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds); return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8);
} }
return null; return null;

@ -1,59 +1,84 @@
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using ServiceStack.Web;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.Net;
using System.Collections.Generic;
using ServiceStack.Web;
namespace MediaBrowser.Api.Playback.Progressive namespace MediaBrowser.Api.Playback.Progressive
{ {
public class ProgressiveFileCopier public class ProgressiveFileCopier : IAsyncStreamSource, IHasOptions
{ {
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly TranscodingJob _job; private readonly TranscodingJob _job;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly string _path;
private readonly CancellationToken _cancellationToken;
private readonly Dictionary<string, string> _outputHeaders;
// 256k // 256k
private const int BufferSize = 262144; private const int BufferSize = 81920;
private long _bytesWritten = 0; private long _bytesWritten = 0;
public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job, ILogger logger) public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken)
{ {
_fileSystem = fileSystem; _fileSystem = fileSystem;
_path = path;
_outputHeaders = outputHeaders;
_job = job; _job = job;
_logger = logger; _logger = logger;
_cancellationToken = cancellationToken;
} }
public async Task StreamFile(string path, Stream outputStream, CancellationToken cancellationToken) public IDictionary<string, string> Options
{ {
var eofCount = 0; get
{
return _outputHeaders;
}
}
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) public async Task WriteToAsync(Stream outputStream)
{
try
{ {
while (eofCount < 15) var eofCount = 0;
using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{ {
var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, cancellationToken).ConfigureAwait(false); while (eofCount < 15)
{
var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false);
//var position = fs.Position; //var position = fs.Position;
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
if (bytesRead == 0) if (bytesRead == 0)
{
if (_job == null || _job.HasExited)
{ {
eofCount++; if (_job == null || _job.HasExited)
{
eofCount++;
}
await Task.Delay(100, _cancellationToken).ConfigureAwait(false);
}
else
{
eofCount = 0;
} }
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
else
{
eofCount = 0;
} }
} }
} }
finally
{
if (_job != null)
{
ApiEntryPoint.Instance.OnTranscodeEndRequest(_job);
}
}
} }
private async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken) private async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)

@ -149,11 +149,11 @@ namespace MediaBrowser.Api.Playback.Progressive
{ {
args += " -copyts -avoid_negative_ts disabled -start_at_zero"; args += " -copyts -avoid_negative_ts disabled -start_at_zero";
} }
return args; return args;
} }
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
5.ToString(UsCulture)); 5.ToString(UsCulture));
args += keyFrameArg; args += keyFrameArg;
@ -237,4 +237,4 @@ namespace MediaBrowser.Api.Playback.Progressive
return args; return args;
} }
} }
} }

@ -74,6 +74,7 @@ namespace MediaBrowser.Api.Playback
public string Params { get; set; } public string Params { get; set; }
public string PlaySessionId { get; set; } public string PlaySessionId { get; set; }
public string LiveStreamId { get; set; } public string LiveStreamId { get; set; }
public string Tag { get; set; }
} }
public class VideoStreamRequest : StreamRequest public class VideoStreamRequest : StreamRequest
@ -192,8 +193,6 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool CopyTimestamps { get; set; } public bool CopyTimestamps { get; set; }
public bool ForceLiveStream { get; set; }
public bool EnableSubtitlesInManifest { get; set; } public bool EnableSubtitlesInManifest { get; set; }
public VideoStreamRequest() public VideoStreamRequest()

@ -80,7 +80,10 @@ namespace MediaBrowser.Api.Playback
{ {
return 10; return 10;
} }
if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1) if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 ||
userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
{ {
return 10; return 10;
} }
@ -208,7 +211,7 @@ namespace MediaBrowser.Api.Playback
private async void DisposeLiveStream() private async void DisposeLiveStream()
{ {
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId)) if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
{ {
try try
{ {

@ -72,7 +72,7 @@ namespace MediaBrowser.Api
} }
[Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")] [Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")]
public class GetPlaylistItems : IReturn<QueryResult<BaseItemDto>>, IHasItemFields public class GetPlaylistItems : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
{ {
[ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
public string Id { get; set; } public string Id { get; set; }
@ -104,6 +104,18 @@ namespace MediaBrowser.Api
/// <value>The fields.</value> /// <value>The fields.</value>
[ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Fields { get; set; } public string Fields { get; set; }
[ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableImages { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
[ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? ImageTypeLimit { get; set; }
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
} }
[Authenticated] [Authenticated]

@ -227,7 +227,7 @@ namespace MediaBrowser.Api
.ToList(); .ToList();
} }
} }
catch (Exception ex) catch
{ {
//Logger.ErrorException("Error getting plugin list", ex); //Logger.ErrorException("Error getting plugin list", ex);
// Play it safe here // Play it safe here

@ -275,8 +275,6 @@ namespace MediaBrowser.Api.Reports
case ItemFilter.IsPlayed: case ItemFilter.IsPlayed:
query.IsPlayed = true; query.IsPlayed = true;
break; break;
case ItemFilter.IsRecentlyAdded:
break;
case ItemFilter.IsResumable: case ItemFilter.IsResumable:
query.IsResumable = true; query.IsResumable = true;
break; break;

@ -29,8 +29,20 @@ namespace MediaBrowser.Api
public string ExcludeArtistIds { get; set; } public string ExcludeArtistIds { get; set; }
} }
public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasItemFields public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasDtoOptions
{ {
[ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableImages { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
[ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? ImageTypeLimit { get; set; }
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
/// <summary> /// <summary>
/// Gets or sets the user id. /// Gets or sets the user id.
/// </summary> /// </summary>

@ -117,7 +117,7 @@ namespace MediaBrowser.Api
config.EnableStandaloneMusicKeys = true; config.EnableStandaloneMusicKeys = true;
config.EnableCaseSensitiveItemIds = true; config.EnableCaseSensitiveItemIds = true;
//config.EnableFolderView = true; //config.EnableFolderView = true;
config.SchemaVersion = 108; config.SchemaVersion = 109;
} }
public void Post(UpdateStartupConfiguration request) public void Post(UpdateStartupConfiguration request)

@ -24,7 +24,20 @@ namespace MediaBrowser.Api.Sync
} }
break; break;
} }
if (item.IsFolder && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre) if (item.IsAudio)
{
options.Add(SyncJobOption.Quality);
options.Add(SyncJobOption.Profile);
break;
}
if (item.IsMusicGenre || item.IsArtist|| item.IsType("musicalbum"))
{
options.Add(SyncJobOption.Quality);
options.Add(SyncJobOption.Profile);
options.Add(SyncJobOption.ItemLimit);
break;
}
if (item.IsFolderItem && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
{ {
options.Add(SyncJobOption.Quality); options.Add(SyncJobOption.Quality);
options.Add(SyncJobOption.Profile); options.Add(SyncJobOption.Profile);
@ -44,7 +57,7 @@ namespace MediaBrowser.Api.Sync
{ {
if (item.SupportsSync ?? false) if (item.SupportsSync ?? false)
{ {
if (item.IsFolder || item.IsGameGenre || item.IsMusicGenre || item.IsGenre || item.IsArtist || item.IsStudio || item.IsPerson) if (item.IsFolderItem || item.IsGameGenre || item.IsMusicGenre || item.IsGenre || item.IsArtist || item.IsStudio || item.IsPerson)
{ {
options.Add(SyncJobOption.SyncNewContent); options.Add(SyncJobOption.SyncNewContent);
options.Add(SyncJobOption.ItemLimit); options.Add(SyncJobOption.ItemLimit);

@ -66,6 +66,7 @@ namespace MediaBrowser.Api.Sync
public string Id { get; set; } public string Id { get; set; }
} }
[Route("/Sync/Items/Cancel", "POST", Summary = "Cancels items from a sync target")]
[Route("/Sync/{TargetId}/Items", "DELETE", Summary = "Cancels items from a sync target")] [Route("/Sync/{TargetId}/Items", "DELETE", Summary = "Cancels items from a sync target")]
public class CancelItems : IReturnVoid public class CancelItems : IReturnVoid
{ {
@ -211,7 +212,7 @@ namespace MediaBrowser.Api.Sync
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }
public void Delete(CancelItems request) public void Any(CancelItems request)
{ {
var itemIds = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var itemIds = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
@ -290,7 +291,8 @@ namespace MediaBrowser.Api.Sync
{ {
Fields = new List<ItemFields> Fields = new List<ItemFields>
{ {
ItemFields.SyncInfo ItemFields.SyncInfo,
ItemFields.BasicSyncInfo
} }
}; };

@ -69,6 +69,9 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; } public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
} }
[Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")] [Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")]
@ -117,6 +120,9 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; } public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
} }
[Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")] [Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")]
@ -184,6 +190,10 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; } public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
} }
[Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")] [Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")]
@ -226,6 +236,10 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; } public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
} }
/// <summary> /// <summary>
@ -409,23 +423,14 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException("No series exists with Id " + request.Id); throw new ResourceNotFoundException("No series exists with Id " + request.Id);
} }
var seasons = series.GetSeasons(user); var seasons = (await series.GetItems(new InternalItemsQuery(user)
if (request.IsSpecialSeason.HasValue)
{ {
var val = request.IsSpecialSeason.Value; IsMissing = request.IsMissing,
IsVirtualUnaired = request.IsVirtualUnaired,
IsSpecialSeason = request.IsSpecialSeason,
AdjacentTo = request.AdjacentTo
seasons = seasons.Where(i => i.IsSpecialSeason == val); }).ConfigureAwait(false)).Items.OfType<Season>();
}
seasons = FilterVirtualSeasons(request, seasons);
// This must be the last filter
if (!string.IsNullOrEmpty(request.AdjacentTo))
{
seasons = UserViewBuilder.FilterForAdjacency(seasons, request.AdjacentTo)
.Cast<Season>();
}
var dtoOptions = GetDtoOptions(request); var dtoOptions = GetDtoOptions(request);
@ -439,23 +444,6 @@ namespace MediaBrowser.Api
}; };
} }
private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items)
{
if (request.IsMissing.HasValue)
{
var val = request.IsMissing.Value;
items = items.Where(i => (i.IsMissingSeason) == val);
}
if (request.IsVirtualUnaired.HasValue)
{
var val = request.IsVirtualUnaired.Value;
items = items.Where(i => i.IsVirtualUnaired == val);
}
return items;
}
public async Task<object> Get(GetEpisodes request) public async Task<object> Get(GetEpisodes request)
{ {
var user = _userManager.GetUserById(request.UserId); var user = _userManager.GetUserById(request.UserId);
@ -490,7 +478,7 @@ namespace MediaBrowser.Api
} }
else else
{ {
episodes = series.GetEpisodes(user, season); episodes = series.GetSeasonEpisodes(user, season);
} }
} }
else else

@ -8,8 +8,6 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using ServiceStack; using ServiceStack;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary namespace MediaBrowser.Api.UserLibrary

@ -164,8 +164,6 @@ namespace MediaBrowser.Api.UserLibrary
case ItemFilter.IsPlayed: case ItemFilter.IsPlayed:
query.IsPlayed = true; query.IsPlayed = true;
break; break;
case ItemFilter.IsRecentlyAdded:
break;
case ItemFilter.IsResumable: case ItemFilter.IsResumable:
query.IsResumable = true; query.IsResumable = true;
break; break;
@ -180,9 +178,10 @@ namespace MediaBrowser.Api.UserLibrary
var result = GetItems(request, query); var result = GetItems(request, query);
var syncProgess = DtoService.GetSyncedItemProgress(dtoOptions);
var dtos = result.Items.Select(i => var dtos = result.Items.Select(i =>
{ {
var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, user); var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, syncProgess, user);
if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes)) if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes))
{ {
@ -213,6 +212,7 @@ namespace MediaBrowser.Api.UserLibrary
dto.AlbumCount = counts.AlbumCount; dto.AlbumCount = counts.AlbumCount;
dto.SongCount = counts.SongCount; dto.SongCount = counts.SongCount;
dto.GameCount = counts.GameCount; dto.GameCount = counts.GameCount;
dto.ArtistCount = counts.ArtistCount;
} }
/// <summary> /// <summary>
@ -325,7 +325,8 @@ namespace MediaBrowser.Api.UserLibrary
tuples = ibnItems.Select(i => new Tuple<BaseItem, List<BaseItem>>(i, new List<BaseItem>())); tuples = ibnItems.Select(i => new Tuple<BaseItem, List<BaseItem>>(i, new List<BaseItem>()));
} }
var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user)); var syncProgess = DtoService.GetSyncedItemProgress(dtoOptions);
var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, syncProgess, user));
result.Items = dtos.Where(i => i != null).ToArray(); result.Items = dtos.Where(i => i != null).ToArray();

@ -226,6 +226,9 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableImages { get; set; } public bool? EnableImages { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
[ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? ImageTypeLimit { get; set; } public int? ImageTypeLimit { get; set; }

@ -4,11 +4,9 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using ServiceStack; using ServiceStack;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary namespace MediaBrowser.Api.UserLibrary

@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities;
using ServiceStack; using ServiceStack;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary namespace MediaBrowser.Api.UserLibrary

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Localization;
@ -158,33 +157,11 @@ namespace MediaBrowser.Api.UserLibrary
folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder(); folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
} }
if (!string.IsNullOrEmpty(request.Ids)) if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null)
{
request.Recursive = true;
var query = GetItemsQuery(request, user);
var result = await folder.GetItems(query).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(request.SortBy))
{
var ids = query.ItemIds.ToList();
// Try to preserve order
result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
}
return result;
}
if (request.Recursive)
{ {
return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
} }
if (user == null)
{
return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
}
var userRoot = item as UserRootFolder; var userRoot = item as UserRootFolder;
if (userRoot == null) if (userRoot == null)
@ -294,8 +271,6 @@ namespace MediaBrowser.Api.UserLibrary
case ItemFilter.IsPlayed: case ItemFilter.IsPlayed:
query.IsPlayed = true; query.IsPlayed = true;
break; break;
case ItemFilter.IsRecentlyAdded:
break;
case ItemFilter.IsResumable: case ItemFilter.IsResumable:
query.IsResumable = true; query.IsResumable = true;
break; break;

@ -8,8 +8,6 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using ServiceStack; using ServiceStack;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary namespace MediaBrowser.Api.UserLibrary

@ -12,6 +12,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Api.UserLibrary namespace MediaBrowser.Api.UserLibrary
{ {
@ -244,6 +246,9 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; } public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
public GetLatestMedia() public GetLatestMedia()
{ {
Limit = 20; Limit = 20;
@ -262,14 +267,16 @@ namespace MediaBrowser.Api.UserLibrary
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly IUserViewManager _userViewManager; private readonly IUserViewManager _userViewManager;
private readonly IFileSystem _fileSystem;
public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager) public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager, IFileSystem fileSystem)
{ {
_userManager = userManager; _userManager = userManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_userDataRepository = userDataRepository; _userDataRepository = userDataRepository;
_dtoService = dtoService; _dtoService = dtoService;
_userViewManager = userViewManager; _userViewManager = userViewManager;
_fileSystem = fileSystem;
} }
/// <summary> /// <summary>
@ -426,12 +433,14 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary> /// </summary>
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
public object Get(GetItem request) public async Task<object> Get(GetItem request)
{ {
var user = _userManager.GetUserById(request.UserId); var user = _userManager.GetUserById(request.UserId);
var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id); var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
var dtoOptions = GetDtoOptions(request); var dtoOptions = GetDtoOptions(request);
var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
@ -439,6 +448,27 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedSerializedResultUsingCache(result); return ToOptimizedSerializedResultUsingCache(result);
} }
private async Task RefreshItemOnDemandIfNeeded(BaseItem item)
{
if (item is Person)
{
var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
if (!hasMetdata)
{
var options = new MetadataRefreshOptions(_fileSystem)
{
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
ImageRefreshMode = ImageRefreshMode.FullRefresh,
ForceSave = performFullRefresh
};
await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
}
}
}
/// <summary> /// <summary>
/// Gets the specified request. /// Gets the specified request.
/// </summary> /// </summary>

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Api namespace MediaBrowser.Api
{ {
@ -81,11 +82,18 @@ namespace MediaBrowser.Api
var dtoOptions = GetDtoOptions(request); var dtoOptions = GetDtoOptions(request);
var video = (Video)item; var video = item as Video;
BaseItemDto[] items;
var items = video.GetAdditionalParts() if (video != null)
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video)) {
.ToArray(); items = video.GetAdditionalParts()
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video))
.ToArray();
}
else
{
items = new BaseItemDto[] { };
}
var result = new ItemsResult var result = new ItemsResult
{ {

@ -55,7 +55,7 @@
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath> <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
</Reference> </Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.5\lib\net45\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Patterns.Logging"> <Reference Include="Patterns.Logging">

@ -429,17 +429,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
GC.Collect(2, GCCollectionMode.Forced, true); GC.Collect(2, GCCollectionMode.Forced, true);
} }
/// <summary>
/// Executes the task.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
/// <returns>Task.</returns>
private Task ExecuteTask(CancellationToken cancellationToken, IProgress<double> progress)
{
return Task.Run(async () => await ScheduledTask.Execute(cancellationToken, progress).ConfigureAwait(false), cancellationToken);
}
/// <summary> /// <summary>
/// Progress_s the progress changed. /// Progress_s the progress changed.
/// </summary> /// </summary>

@ -1,6 +1,7 @@
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Xml; using System.Xml;
using CommonIO; using CommonIO;
@ -24,13 +25,22 @@ namespace MediaBrowser.Common.Implementations.Serialization
// Need to cache these // Need to cache these
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
private readonly ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer> _serializers = private readonly Dictionary<string, System.Xml.Serialization.XmlSerializer> _serializers =
new ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer>(); new Dictionary<string, System.Xml.Serialization.XmlSerializer>();
private System.Xml.Serialization.XmlSerializer GetSerializer(Type type) private System.Xml.Serialization.XmlSerializer GetSerializer(Type type)
{ {
var key = type.FullName; var key = type.FullName;
return _serializers.GetOrAdd(key, k => new System.Xml.Serialization.XmlSerializer(type)); lock (_serializers)
{
System.Xml.Serialization.XmlSerializer serializer;
if (!_serializers.TryGetValue(key, out serializer))
{
serializer = new System.Xml.Serialization.XmlSerializer(type);
_serializers[key] = serializer;
}
return serializer;
}
} }
/// <summary> /// <summary>

@ -33,7 +33,6 @@ namespace MediaBrowser.Common.Implementations.Updates
EnableKeepAlive = false, EnableKeepAlive = false,
CancellationToken = cancellationToken, CancellationToken = cancellationToken,
UserAgent = "Emby/3.0" UserAgent = "Emby/3.0"
}; };
if (_cacheLength.Ticks > 0) if (_cacheLength.Ticks > 0)
@ -79,6 +78,69 @@ namespace MediaBrowser.Common.Implementations.Updates
}; };
} }
private bool MatchesUpdateLevel(RootObject i, PackageVersionClass updateLevel)
{
if (updateLevel == PackageVersionClass.Beta)
{
return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase);
}
if (updateLevel == PackageVersionClass.Dev)
{
return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) ||
i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
}
// Technically all we need to do is check that it's not pre-release
// But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
return !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) &&
!i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
}
public async Task<List<RootObject>> GetLatestReleases(string organzation, string repository, string assetFilename, CancellationToken cancellationToken)
{
var list = new List<RootObject>();
var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
var options = new HttpRequestOptions
{
Url = url,
EnableKeepAlive = false,
CancellationToken = cancellationToken,
UserAgent = "Emby/3.0"
};
if (_cacheLength.Ticks > 0)
{
options.CacheMode = CacheMode.Unconditional;
options.CacheLength = _cacheLength;
}
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
{
var obj = _jsonSerializer.DeserializeFromStream<RootObject[]>(stream);
obj = obj.Where(i => (i.assets ?? new List<Asset>()).Any(a => IsAsset(a, assetFilename))).ToArray();
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Release)).OrderByDescending(GetVersion).Take(1));
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Beta)).OrderByDescending(GetVersion).Take(1));
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Dev)).OrderByDescending(GetVersion).Take(1));
return list;
}
}
public Version GetVersion(RootObject obj)
{
Version version;
if (!Version.TryParse(obj.tag_name, out version))
{
return new Version(1, 0);
}
return version;
}
private CheckForUpdateResult CheckForUpdateResult(RootObject obj, Version minVersion, string assetFilename, string packageName, string targetFilename) private CheckForUpdateResult CheckForUpdateResult(RootObject obj, Version minVersion, string assetFilename, string packageName, string targetFilename)
{ {
Version version; Version version;

@ -2,7 +2,7 @@
<packages> <packages>
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" /> <package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" /> <package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="NLog" version="4.3.5" targetFramework="net45" /> <package id="NLog" version="4.3.6" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" /> <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" /> <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
</packages> </packages>

@ -9,11 +9,11 @@ namespace MediaBrowser.Common.IO
/// <summary> /// <summary>
/// The default copy to buffer size /// The default copy to buffer size
/// </summary> /// </summary>
public const int DefaultCopyToBufferSize = 262144; public const int DefaultCopyToBufferSize = 81920;
/// <summary> /// <summary>
/// The default file stream buffer size /// The default file stream buffer size
/// </summary> /// </summary>
public const int DefaultFileStreamBufferSize = 262144; public const int DefaultFileStreamBufferSize = 81920;
} }
} }

@ -211,7 +211,7 @@ namespace MediaBrowser.Common.Plugins
{ {
return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType)); return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
} }
catch (Exception ex) catch
{ {
return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType)); return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
} }

@ -57,10 +57,7 @@ namespace MediaBrowser.Controller.Channels
catch catch
{ {
// Already logged at lower levels // Already logged at lower levels
return new QueryResult<BaseItem> return new QueryResult<BaseItem>();
{
};
} }
} }

@ -83,8 +83,7 @@ namespace MediaBrowser.Controller.Channels
{ {
var list = new List<MediaStream>(); var list = new List<MediaStream>();
if (!string.IsNullOrWhiteSpace(info.VideoCodec) && if (!string.IsNullOrWhiteSpace(info.VideoCodec))
!string.IsNullOrWhiteSpace(info.AudioCodec))
{ {
list.Add(new MediaStream list.Add(new MediaStream
{ {
@ -99,7 +98,10 @@ namespace MediaBrowser.Controller.Channels
BitRate = info.VideoBitrate, BitRate = info.VideoBitrate,
AverageFrameRate = info.Framerate AverageFrameRate = info.Framerate
}); });
}
if (!string.IsNullOrWhiteSpace(info.AudioCodec))
{
list.Add(new MediaStream list.Add(new MediaStream
{ {
Type = MediaStreamType.Audio, Type = MediaStreamType.Audio,

@ -19,12 +19,16 @@ namespace MediaBrowser.Controller.Dto
public bool EnableImages { get; set; } public bool EnableImages { get; set; }
public bool AddProgramRecordingInfo { get; set; } public bool AddProgramRecordingInfo { get; set; }
public string DeviceId { get; set; } public string DeviceId { get; set; }
public bool EnableUserData { get; set; }
public bool AddCurrentProgram { get; set; }
public DtoOptions() public DtoOptions()
{ {
Fields = new List<ItemFields>(); Fields = new List<ItemFields>();
ImageTypeLimit = int.MaxValue; ImageTypeLimit = int.MaxValue;
EnableImages = true; EnableImages = true;
EnableUserData = true;
AddCurrentProgram = true;
Fields = Enum.GetNames(typeof (ItemFields)) Fields = Enum.GetNames(typeof (ItemFields))
.Select(i => (ItemFields) Enum.Parse(typeof (ItemFields), i, true)) .Select(i => (ItemFields) Enum.Parse(typeof (ItemFields), i, true))

@ -1,9 +1,9 @@
using System; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Sync;
namespace MediaBrowser.Controller.Dto namespace MediaBrowser.Controller.Dto
{ {
@ -43,14 +43,6 @@ namespace MediaBrowser.Controller.Dto
/// <returns>Task{BaseItemDto}.</returns> /// <returns>Task{BaseItemDto}.</returns>
BaseItemDto GetBaseItemDto(BaseItem item, List<ItemFields> fields, User user = null, BaseItem owner = null); BaseItemDto GetBaseItemDto(BaseItem item, List<ItemFields> fields, User user = null, BaseItem owner = null);
/// <summary>
/// Fills the synchronize information.
/// </summary>
/// <param name="tuples">The tuples.</param>
/// <param name="options">The options.</param>
/// <param name="user">The user.</param>
void FillSyncInfo(IEnumerable<Tuple<BaseItem, BaseItemDto>> tuples, DtoOptions options, User user);
/// <summary> /// <summary>
/// Gets the base item dto. /// Gets the base item dto.
/// </summary> /// </summary>
@ -89,11 +81,8 @@ namespace MediaBrowser.Controller.Dto
/// <summary> /// <summary>
/// Gets the item by name dto. /// Gets the item by name dto.
/// </summary> /// </summary>
/// <param name="item">The item.</param> BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, Dictionary<string, SyncedItemProgress> syncProgress, User user = null);
/// <param name="options">The options.</param>
/// <param name="taggedItems">The tagged items.</param> Dictionary<string, SyncedItemProgress> GetSyncedItemProgress(DtoOptions options);
/// <param name="user">The user.</param>
/// <returns>BaseItemDto.</returns>
BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null);
} }
} }

@ -5,6 +5,8 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -67,6 +69,31 @@ namespace MediaBrowser.Controller.Entities
return CreateResolveArgs(directoryService, true).FileSystemChildren; return CreateResolveArgs(directoryService, true).FileSystemChildren;
} }
private List<Guid> _childrenIds = null;
private readonly object _childIdsLock = new object();
protected override IEnumerable<BaseItem> LoadChildren()
{
lock (_childIdsLock)
{
if (_childrenIds == null || _childrenIds.Count == 0)
{
var list = base.LoadChildren().ToList();
_childrenIds = list.Select(i => i.Id).ToList();
return list;
}
return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
}
}
private void ClearCache()
{
lock (_childIdsLock)
{
_childrenIds = null;
}
}
private bool _requiresRefresh; private bool _requiresRefresh;
public override bool RequiresRefresh() public override bool RequiresRefresh()
{ {
@ -76,7 +103,7 @@ namespace MediaBrowser.Controller.Entities
{ {
var locations = PhysicalLocations.ToList(); var locations = PhysicalLocations.ToList();
var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList(); var newLocations = CreateResolveArgs(new DirectoryService(Logger, FileSystem), false).PhysicalLocations.ToList();
if (!locations.SequenceEqual(newLocations)) if (!locations.SequenceEqual(newLocations))
{ {
@ -89,6 +116,8 @@ namespace MediaBrowser.Controller.Entities
public override bool BeforeMetadataRefresh() public override bool BeforeMetadataRefresh()
{ {
ClearCache();
var changed = base.BeforeMetadataRefresh() || _requiresRefresh; var changed = base.BeforeMetadataRefresh() || _requiresRefresh;
_requiresRefresh = false; _requiresRefresh = false;
return changed; return changed;
@ -96,9 +125,11 @@ namespace MediaBrowser.Controller.Entities
private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations) private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
{ {
ClearCache();
var path = ContainingFolderPath; var path = ContainingFolderPath;
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths , directoryService) var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
{ {
FileInfo = FileSystem.GetDirectoryInfo(path), FileInfo = FileSystem.GetDirectoryInfo(path),
Path = path, Path = path,
@ -135,7 +166,22 @@ namespace MediaBrowser.Controller.Entities
return args; return args;
} }
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren);
}
protected override async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
ClearCache();
await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService)
.ConfigureAwait(false);
ClearCache();
}
/// <summary> /// <summary>
/// Adds the virtual child. /// Adds the virtual child.
/// </summary> /// </summary>
@ -151,15 +197,6 @@ namespace MediaBrowser.Controller.Entities
_virtualChildren.Add(child); _virtualChildren.Add(child);
} }
/// <summary>
/// Get the children of this folder from the actual file system
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren);
}
/// <summary> /// <summary>
/// Finds the virtual child. /// Finds the virtual child.
/// </summary> /// </summary>

@ -5,9 +5,11 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Threading; using System.Threading;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
namespace MediaBrowser.Controller.Entities.Audio namespace MediaBrowser.Controller.Entities.Audio
@ -47,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Audio
} }
[IgnoreDataMember] [IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange public override bool EnableRefreshOnDateModifiedChange
{ {
get { return true; } get { return true; }
} }
@ -266,6 +268,11 @@ namespace MediaBrowser.Controller.Entities.Audio
Size = i.Size Size = i.Size
}; };
if (info.Protocol == MediaProtocol.File)
{
info.ETag = i.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N");
}
if (string.IsNullOrEmpty(info.Container)) if (string.IsNullOrEmpty(info.Container))
{ {
if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual) if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)

@ -169,13 +169,9 @@ namespace MediaBrowser.Controller.Entities.Audio
list.Add("Artist-" + (item.Name ?? string.Empty).RemoveDiacritics()); list.Add("Artist-" + (item.Name ?? string.Empty).RemoveDiacritics());
return list; return list;
} }
public override string CreatePresentationUniqueKey()
public override string PresentationUniqueKey
{ {
get return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
{
return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
}
} }
protected override bool GetBlockUnratedValue(UserPolicy config) protected override bool GetBlockUnratedValue(UserPolicy config)
{ {
@ -274,5 +270,54 @@ namespace MediaBrowser.Controller.Entities.Audio
return false; return false;
} }
} }
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.ArtistsPath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
if (IsAccessedByName)
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
if (IsAccessedByName)
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
}
return hasChanges;
}
} }
} }

@ -18,13 +18,9 @@ namespace MediaBrowser.Controller.Entities.Audio
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list; return list;
} }
public override string CreatePresentationUniqueKey()
public override string PresentationUniqueKey
{ {
get return GetUserDataKeys()[0];
{
return GetUserDataKeys()[0];
}
} }
[IgnoreDataMember] [IgnoreDataMember]
@ -96,5 +92,48 @@ namespace MediaBrowser.Controller.Entities.Audio
return LibraryManager.GetItemList(query); return LibraryManager.GetItemList(query);
} }
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.MusicGenrePath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
} }
} }

@ -281,6 +281,20 @@ namespace MediaBrowser.Controller.Entities
} }
} }
public Task UpdateIsOffline(bool newValue)
{
var item = this;
if (item.IsOffline != newValue)
{
item.IsOffline = newValue;
// this is creating too many repeated db updates
//return item.UpdateToRepository(ItemUpdateType.None, CancellationToken.None);
}
return Task.FromResult(true);
}
/// <summary> /// <summary>
/// Gets or sets the type of the location. /// Gets or sets the type of the location.
/// </summary> /// </summary>
@ -290,10 +304,10 @@ namespace MediaBrowser.Controller.Entities
{ {
get get
{ {
if (IsOffline) //if (IsOffline)
{ //{
return LocationType.Offline; // return LocationType.Offline;
} //}
if (string.IsNullOrWhiteSpace(Path)) if (string.IsNullOrWhiteSpace(Path))
{ {
@ -455,7 +469,7 @@ namespace MediaBrowser.Controller.Entities
public DateTime DateLastRefreshed { get; set; } public DateTime DateLastRefreshed { get; set; }
[IgnoreDataMember] [IgnoreDataMember]
public virtual bool EnableForceSaveOnDateModifiedChange public virtual bool EnableRefreshOnDateModifiedChange
{ {
get { return false; } get { return false; }
} }
@ -767,6 +781,9 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember] [IgnoreDataMember]
public string OfficialRating { get; set; } public string OfficialRating { get; set; }
[IgnoreDataMember]
public int InheritedParentalRatingValue { get; set; }
/// <summary> /// <summary>
/// Gets or sets the critic rating. /// Gets or sets the critic rating.
/// </summary> /// </summary>
@ -951,7 +968,7 @@ namespace MediaBrowser.Controller.Entities
.Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase)) .Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
); );
return LibraryManager.ResolvePaths(files, directoryService, null) return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
.OfType<Audio.Audio>() .OfType<Audio.Audio>()
.Select(audio => .Select(audio =>
{ {
@ -981,7 +998,7 @@ namespace MediaBrowser.Controller.Entities
.Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) .Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => directoryService.GetFiles(i.FullName)); .SelectMany(i => directoryService.GetFiles(i.FullName));
return LibraryManager.ResolvePaths(files, directoryService, null) return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
.OfType<Video>() .OfType<Video>()
.Select(item => .Select(item =>
{ {
@ -1003,7 +1020,7 @@ namespace MediaBrowser.Controller.Entities
public Task RefreshMetadata(CancellationToken cancellationToken) public Task RefreshMetadata(CancellationToken cancellationToken)
{ {
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken); return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)), cancellationToken);
} }
/// <summary> /// <summary>
@ -1194,10 +1211,17 @@ namespace MediaBrowser.Controller.Entities
get { return null; } get { return null; }
} }
public virtual string CreatePresentationUniqueKey()
{
return Id.ToString("N");
}
[IgnoreDataMember] [IgnoreDataMember]
public virtual string PresentationUniqueKey public string PresentationUniqueKey { get; set; }
public string GetPresentationUniqueKey()
{ {
get { return Id.ToString("N"); } return PresentationUniqueKey ?? CreatePresentationUniqueKey();
} }
public virtual bool RequiresRefresh() public virtual bool RequiresRefresh()
@ -2206,6 +2230,15 @@ namespace MediaBrowser.Controller.Entities
} }
} }
[IgnoreDataMember]
public virtual bool StopRefreshIfLocalMetadataFound
{
get
{
return true;
}
}
public virtual IEnumerable<Guid> GetIdsForAncestorQuery() public virtual IEnumerable<Guid> GetIdsForAncestorQuery()
{ {
return new[] { Id }; return new[] { Id };

@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Entities
} }
[IgnoreDataMember] [IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange public override bool EnableRefreshOnDateModifiedChange
{ {
get { return true; } get { return true; }
} }

@ -3,11 +3,15 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Serialization;
using MoreLinq; using MoreLinq;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
@ -18,6 +22,8 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class CollectionFolder : Folder, ICollectionFolder public class CollectionFolder : Folder, ICollectionFolder
{ {
public static IXmlSerializer XmlSerializer { get; set; }
public CollectionFolder() public CollectionFolder()
{ {
PhysicalLocationsList = new List<string>(); PhysicalLocationsList = new List<string>();
@ -39,6 +45,72 @@ namespace MediaBrowser.Controller.Entities
public string CollectionType { get; set; } public string CollectionType { get; set; }
private static readonly Dictionary<string, LibraryOptions> LibraryOptions = new Dictionary<string, LibraryOptions>();
public LibraryOptions GetLibraryOptions()
{
lock (LibraryOptions)
{
LibraryOptions options;
if (!LibraryOptions.TryGetValue(Path, out options))
{
options = LoadLibraryOptions();
LibraryOptions[Path] = options;
}
return options;
}
}
private LibraryOptions LoadLibraryOptions()
{
try
{
var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(Path)) as LibraryOptions;
if (result == null)
{
return new LibraryOptions();
}
return result;
}
catch (FileNotFoundException)
{
return new LibraryOptions();
}
catch (DirectoryNotFoundException)
{
return new LibraryOptions();
}
catch (Exception ex)
{
Logger.ErrorException("Error loading library options", ex);
return new LibraryOptions();
}
}
private static string GetLibraryOptionsPath(string path)
{
return System.IO.Path.Combine(path, "options.xml");
}
public void UpdateLibraryOptions(LibraryOptions options)
{
SaveLibraryOptions(Path, options);
}
public static void SaveLibraryOptions(string path, LibraryOptions options)
{
lock (LibraryOptions)
{
LibraryOptions[path] = options;
options.SchemaVersion = 1;
XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path));
}
}
/// <summary> /// <summary>
/// Allow different display preferences for each collection folder /// Allow different display preferences for each collection folder
/// </summary> /// </summary>
@ -82,7 +154,7 @@ namespace MediaBrowser.Controller.Entities
{ {
var locations = PhysicalLocations.ToList(); var locations = PhysicalLocations.ToList();
var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList(); var newLocations = CreateResolveArgs(new DirectoryService(Logger, FileSystem), false).PhysicalLocations.ToList();
if (!locations.SequenceEqual(newLocations)) if (!locations.SequenceEqual(newLocations))
{ {

@ -1,5 +1,4 @@
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
@ -14,6 +13,8 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Channels; using MediaBrowser.Model.Channels;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
@ -273,13 +274,14 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
protected virtual IEnumerable<BaseItem> LoadChildren() protected virtual IEnumerable<BaseItem> LoadChildren()
{ {
//Logger.Debug("Loading children from {0} {1} {2}", GetType().Name, Id, Path);
//just load our children from the repo - the library will be validated and maintained in other processes //just load our children from the repo - the library will be validated and maintained in other processes
return GetCachedChildren(); return GetCachedChildren();
} }
public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken) public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken)
{ {
return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(FileSystem))); return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)));
} }
/// <summary> /// <summary>
@ -373,7 +375,7 @@ namespace MediaBrowser.Controller.Entities
if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child)) if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child))
{ {
await UpdateIsOffline(currentChild, false).ConfigureAwait(false); await currentChild.UpdateIsOffline(false).ConfigureAwait(false);
validChildren.Add(currentChild); validChildren.Add(currentChild);
continue; continue;
@ -402,7 +404,7 @@ namespace MediaBrowser.Controller.Entities
else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path)) else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
{ {
await UpdateIsOffline(item, true).ConfigureAwait(false); await item.UpdateIsOffline(true).ConfigureAwait(false);
} }
else else
{ {
@ -459,17 +461,6 @@ namespace MediaBrowser.Controller.Entities
progress.Report(100); progress.Report(100);
} }
private Task UpdateIsOffline(BaseItem item, bool newValue)
{
if (item.IsOffline != newValue)
{
item.IsOffline = newValue;
return item.UpdateToRepository(ItemUpdateType.None, CancellationToken.None);
}
return Task.FromResult(true);
}
private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
{ {
var children = ActualChildren.ToList(); var children = ActualChildren.ToList();
@ -643,8 +634,9 @@ namespace MediaBrowser.Controller.Entities
protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{ {
var collectionType = LibraryManager.GetContentType(this); var collectionType = LibraryManager.GetContentType(this);
var libraryOptions = LibraryManager.GetLibraryOptions(this);
return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, collectionType); return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, libraryOptions, collectionType);
} }
/// <summary> /// <summary>
@ -699,7 +691,7 @@ namespace MediaBrowser.Controller.Entities
items = GetRecursiveChildren(user, query); items = GetRecursiveChildren(user, query);
} }
return PostFilterAndSort(items, query); return PostFilterAndSort(items, query, true, true);
} }
if (!(this is UserRootFolder) && !(this is AggregateFolder)) if (!(this is UserRootFolder) && !(this is AggregateFolder))
@ -883,6 +875,15 @@ namespace MediaBrowser.Controller.Entities
return true; return true;
} }
if (query.IsPlayed.HasValue)
{
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(typeof(Series).Name))
{
Logger.Debug("Query requires post-filtering due to IsPlayed");
return true;
}
}
return false; return false;
} }
@ -890,8 +891,16 @@ namespace MediaBrowser.Controller.Entities
{ {
if (query.ItemIds.Length > 0) if (query.ItemIds.Length > 0)
{ {
var specificItems = query.ItemIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList(); var result = LibraryManager.GetItemsResult(query);
return Task.FromResult(PostFilterAndSort(specificItems, query));
if (query.SortBy.Length == 0)
{
var ids = query.ItemIds.ToList();
// Try to preserve order
result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
}
return Task.FromResult(result);
} }
return GetItemsInternal(query); return GetItemsInternal(query);
@ -919,10 +928,7 @@ namespace MediaBrowser.Controller.Entities
catch catch
{ {
// Already logged at lower levels // Already logged at lower levels
return new QueryResult<BaseItem> return new QueryResult<BaseItem>();
{
};
} }
} }
@ -950,12 +956,12 @@ namespace MediaBrowser.Controller.Entities
: GetChildren(user, true).Where(filter); : GetChildren(user, true).Where(filter);
} }
return PostFilterAndSort(items, query); return PostFilterAndSort(items, query, true, true);
} }
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query) protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool collapseBoxSetItems, bool enableSorting)
{ {
return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager); return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager, collapseBoxSetItems, enableSorting);
} }
public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren) public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
@ -1419,7 +1425,7 @@ namespace MediaBrowser.Controller.Entities
itemDto.RecursiveItemCount = allItemsQueryResult.TotalRecordCount; itemDto.RecursiveItemCount = allItemsQueryResult.TotalRecordCount;
} }
double recursiveItemCount = allItemsQueryResult.TotalRecordCount; var recursiveItemCount = allItemsQueryResult.TotalRecordCount;
double unplayedCount = unplayedQueryResult.TotalRecordCount; double unplayedCount = unplayedQueryResult.TotalRecordCount;
if (recursiveItemCount > 0) if (recursiveItemCount > 0)
@ -1429,6 +1435,14 @@ namespace MediaBrowser.Controller.Entities
dto.Played = dto.PlayedPercentage.Value >= 100; dto.Played = dto.PlayedPercentage.Value >= 100;
dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount; dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
} }
if (itemDto != null)
{
if (this is Season || this is MusicAlbum)
{
itemDto.ChildCount = recursiveItemCount;
}
}
} }
} }
} }

@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Entities
} }
[IgnoreDataMember] [IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange public override bool EnableRefreshOnDateModifiedChange
{ {
get { return true; } get { return true; }
} }

@ -16,12 +16,9 @@ namespace MediaBrowser.Controller.Entities
return list; return list;
} }
public override string PresentationUniqueKey public override string CreatePresentationUniqueKey()
{ {
get return GetUserDataKeys()[0];
{
return GetUserDataKeys()[0];
}
} }
/// <summary> /// <summary>
@ -87,5 +84,48 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
} }
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GameGenrePath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
} }
} }

@ -19,13 +19,9 @@ namespace MediaBrowser.Controller.Entities
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list; return list;
} }
public override string CreatePresentationUniqueKey()
public override string PresentationUniqueKey
{ {
get return GetUserDataKeys()[0];
{
return GetUserDataKeys()[0];
}
} }
/// <summary> /// <summary>
@ -91,5 +87,48 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
} }
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GenrePath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
} }
} }

@ -1,5 +1,4 @@
using System; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;

@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
/// <value>The date last refreshed.</value> /// <value>The date last refreshed.</value>
DateTime DateLastRefreshed { get; set; } DateTime DateLastRefreshed { get; set; }
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made /// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary> /// </summary>
@ -52,6 +52,15 @@ namespace MediaBrowser.Controller.Entities
bool RequiresRefresh(); bool RequiresRefresh();
bool EnableForceSaveOnDateModifiedChange { get; } bool EnableRefreshOnDateModifiedChange { get; }
string PresentationUniqueKey { get; set; }
string GetPresentationUniqueKey();
string CreatePresentationUniqueKey();
bool StopRefreshIfLocalMetadataFound { get; }
int? GetInheritedParentalRatingValue();
int InheritedParentalRatingValue { get; set; }
} }
} }

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {

@ -37,6 +37,7 @@ namespace MediaBrowser.Controller.Entities
public string[] Genres { get; set; } public string[] Genres { get; set; }
public string[] Keywords { get; set; } public string[] Keywords { get; set; }
public bool? IsSpecialSeason { get; set; }
public bool? IsMissing { get; set; } public bool? IsMissing { get; set; }
public bool? IsUnaired { get; set; } public bool? IsUnaired { get; set; }
public bool? IsVirtualUnaired { get; set; } public bool? IsVirtualUnaired { get; set; }
@ -50,6 +51,7 @@ namespace MediaBrowser.Controller.Entities
public string PresentationUniqueKey { get; set; } public string PresentationUniqueKey { get; set; }
public string Path { get; set; } public string Path { get; set; }
public string PathNotStartsWith { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string SlugName { get; set; } public string SlugName { get; set; }

@ -11,11 +11,13 @@ namespace MediaBrowser.Controller.Entities
public int? MaxListOrder { get; set; } public int? MaxListOrder { get; set; }
public Guid AppearsInItemId { get; set; } public Guid AppearsInItemId { get; set; }
public string NameContains { get; set; } public string NameContains { get; set; }
public SourceType[] SourceTypes { get; set; }
public InternalPeopleQuery() public InternalPeopleQuery()
{ {
PersonTypes = new List<string>(); PersonTypes = new List<string>();
ExcludePersonTypes = new List<string>(); ExcludePersonTypes = new List<string>();
SourceTypes = new SourceType[] { };
} }
} }
} }

@ -62,6 +62,26 @@ namespace MediaBrowser.Controller.Entities.Movies
return UnratedItem.Movie; return UnratedItem.Movie;
} }
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
if (IsLegacyBoxSet)
{
return base.GetNonCachedChildren(directoryService);
}
return new List<BaseItem>();
}
protected override IEnumerable<BaseItem> LoadChildren()
{
if (IsLegacyBoxSet)
{
return base.LoadChildren();
}
// Save a trip to the database
return new List<BaseItem>();
}
[IgnoreDataMember] [IgnoreDataMember]
public override bool IsPreSorted public override bool IsPreSorted
{ {
@ -76,7 +96,21 @@ namespace MediaBrowser.Controller.Entities.Movies
{ {
get get
{ {
return true; if (IsLegacyBoxSet)
{
return true;
}
return false;
}
}
[IgnoreDataMember]
private bool IsLegacyBoxSet
{
get
{
return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
} }
} }

@ -179,5 +179,15 @@ namespace MediaBrowser.Controller.Entities.Movies
return list; return list;
} }
[IgnoreDataMember]
public override bool StopRefreshIfLocalMetadataFound
{
get
{
// Need people id's from internet metadata
return false;
}
}
} }
} }

@ -1,7 +1,6 @@
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Runtime.Serialization;

@ -26,13 +26,9 @@ namespace MediaBrowser.Controller.Entities
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list; return list;
} }
public override string CreatePresentationUniqueKey()
public override string PresentationUniqueKey
{ {
get return GetUserDataKeys()[0];
{
return GetUserDataKeys()[0];
}
} }
public PersonLookupInfo GetLookupInfo() public PersonLookupInfo GetLookupInfo()
@ -126,6 +122,64 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
} }
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validFilename = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
string subFolderPrefix = null;
foreach (char c in validFilename)
{
if (char.IsLetterOrDigit(c))
{
subFolderPrefix = c.ToString();
break;
}
}
var path = ConfigurationManager.ApplicationPaths.PeoplePath;
return string.IsNullOrEmpty(subFolderPrefix) ?
System.IO.Path.Combine(path, validFilename) :
System.IO.Path.Combine(path, subFolderPrefix, validFilename);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
} }
/// <summary> /// <summary>

@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Entities
} }
[IgnoreDataMember] [IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange public override bool EnableRefreshOnDateModifiedChange
{ {
get { return true; } get { return true; }
} }

@ -18,13 +18,9 @@ namespace MediaBrowser.Controller.Entities
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list; return list;
} }
public override string CreatePresentationUniqueKey()
public override string PresentationUniqueKey
{ {
get return GetUserDataKeys()[0];
{
return GetUserDataKeys()[0];
}
} }
/// <summary> /// <summary>
@ -89,5 +85,48 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
} }
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.StudioPath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
} }
} }

@ -135,7 +135,11 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember] [IgnoreDataMember]
public Series Series public Series Series
{ {
get { return FindParent<Series>(); } get
{
var seriesId = SeriesId ?? FindSeriesId();
return seriesId.HasValue ? (LibraryManager.GetItemById(seriesId.Value) as Series) : null;
}
} }
[IgnoreDataMember] [IgnoreDataMember]
@ -143,24 +147,8 @@ namespace MediaBrowser.Controller.Entities.TV
{ {
get get
{ {
var season = FindParent<Season>(); var seasonId = SeasonId ?? FindSeasonId();
return seasonId.HasValue ? (LibraryManager.GetItemById(seasonId.Value) as Season) : null;
// Episodes directly in series folder
if (season == null)
{
var series = Series;
if (series != null && ParentIndexNumber.HasValue)
{
var findNumber = ParentIndexNumber.Value;
season = series.Children
.OfType<Season>()
.FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
}
}
return season;
} }
} }
@ -193,7 +181,23 @@ namespace MediaBrowser.Controller.Entities.TV
public Guid? FindSeasonId() public Guid? FindSeasonId()
{ {
var season = Season; var season = FindParent<Season>();
// Episodes directly in series folder
if (season == null)
{
var series = Series;
if (series != null && ParentIndexNumber.HasValue)
{
var findNumber = ParentIndexNumber.Value;
season = series.Children
.OfType<Season>()
.FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
}
}
return season == null ? (Guid?)null : season.Id; return season == null ? (Guid?)null : season.Id;
} }
@ -263,7 +267,7 @@ namespace MediaBrowser.Controller.Entities.TV
public Guid? FindSeriesId() public Guid? FindSeriesId()
{ {
var series = Series; var series = FindParent<Series>();
return series == null ? (Guid?)null : series.Id; return series == null ? (Guid?)null : series.Id;
} }

@ -1,6 +1,5 @@
using System; using System;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
using MoreLinq; using MoreLinq;
@ -86,7 +85,11 @@ namespace MediaBrowser.Controller.Entities.TV
public override int GetChildCount(User user) public override int GetChildCount(User user)
{ {
return GetChildren(user, true).Count(); Logger.Debug("Season {0} getting child cound", (Path ?? Name));
var result = GetChildren(user, true).Count();
Logger.Debug("Season {0} child cound: ", result);
return result;
} }
/// <summary> /// <summary>
@ -96,7 +99,11 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember] [IgnoreDataMember]
public Series Series public Series Series
{ {
get { return FindParent<Series>(); } get
{
var seriesId = SeriesId ?? FindSeriesId();
return seriesId.HasValue ? (LibraryManager.GetItemById(seriesId.Value) as Series) : null;
}
} }
[IgnoreDataMember] [IgnoreDataMember]
@ -115,22 +122,18 @@ namespace MediaBrowser.Controller.Entities.TV
} }
} }
[IgnoreDataMember] public override string CreatePresentationUniqueKey()
public override string PresentationUniqueKey
{ {
get if (IndexNumber.HasValue)
{ {
if (IndexNumber.HasValue) var series = Series;
if (series != null)
{ {
var series = Series; return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000");
if (series != null)
{
return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000");
}
} }
return base.PresentationUniqueKey;
} }
return base.CreatePresentationUniqueKey();
} }
/// <summary> /// <summary>
@ -142,24 +145,6 @@ namespace MediaBrowser.Controller.Entities.TV
return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name; return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name;
} }
[IgnoreDataMember]
public bool IsMissingSeason
{
get { return (IsVirtualItem) && !IsUnaired; }
}
[IgnoreDataMember]
public bool IsVirtualUnaired
{
get { return (IsVirtualItem) && IsUnaired; }
}
[IgnoreDataMember]
public bool IsSpecialSeason
{
get { return (IndexNumber ?? -1) == 0; }
}
protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query) protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{ {
if (query.User == null) if (query.User == null)
@ -171,10 +156,15 @@ namespace MediaBrowser.Controller.Entities.TV
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
var id = Guid.NewGuid().ToString("N");
Logger.Debug("Season.GetItemsInternal entering GetEpisodes. Request id: " + id);
var items = GetEpisodes(user).Where(filter); var items = GetEpisodes(user).Where(filter);
var result = PostFilterAndSort(items, query); Logger.Debug("Season.GetItemsInternal entering PostFilterAndSort. Request id: " + id);
var result = PostFilterAndSort(items, query, false, false);
Logger.Debug("Season.GetItemsInternal complete. Request id: " + id);
return Task.FromResult(result); return Task.FromResult(result);
} }
@ -185,19 +175,17 @@ namespace MediaBrowser.Controller.Entities.TV
/// <returns>IEnumerable{Episode}.</returns> /// <returns>IEnumerable{Episode}.</returns>
public IEnumerable<Episode> GetEpisodes(User user) public IEnumerable<Episode> GetEpisodes(User user)
{ {
var config = user.Configuration; return GetEpisodes(Series, user);
return GetEpisodes(Series, user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
} }
public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) public IEnumerable<Episode> GetEpisodes(Series series, User user)
{ {
return GetEpisodes(series, user, includeMissingEpisodes, includeVirtualUnairedEpisodes, null); return GetEpisodes(series, user, null);
} }
public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes) public IEnumerable<Episode> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes)
{ {
return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes, allSeriesEpisodes); return series.GetSeasonEpisodes(user, this, allSeriesEpisodes);
} }
public IEnumerable<Episode> GetEpisodes() public IEnumerable<Episode> GetEpisodes()
@ -257,7 +245,7 @@ namespace MediaBrowser.Controller.Entities.TV
public Guid? FindSeriesId() public Guid? FindSeriesId()
{ {
var series = Series; var series = FindParent<Series>();
return series == null ? (Guid?)null : series.Id; return series == null ? (Guid?)null : series.Id;
} }

@ -96,19 +96,29 @@ namespace MediaBrowser.Controller.Entities.TV
} }
} }
[IgnoreDataMember] public override string CreatePresentationUniqueKey()
public override string PresentationUniqueKey
{ {
get var userdatakeys = GetUserDataKeys();
if (userdatakeys.Count > 1)
{ {
var userdatakeys = GetUserDataKeys(); return AddLibrariesToPresentationUniqueKey(userdatakeys[0]);
}
return base.CreatePresentationUniqueKey();
}
if (userdatakeys.Count > 1) private string AddLibrariesToPresentationUniqueKey(string key)
{ {
return userdatakeys[0]; var folders = LibraryManager.GetCollectionFolders(this)
} .Select(i => i.Id.ToString("N"))
return base.PresentationUniqueKey; .ToArray();
if (folders.Length == 0)
{
return key;
} }
return key + "-" + string.Join("-", folders);
} }
private static string GetUniqueSeriesKey(BaseItem series) private static string GetUniqueSeriesKey(BaseItem series)
@ -117,7 +127,7 @@ namespace MediaBrowser.Controller.Entities.TV
{ {
return series.Id.ToString("N"); return series.Id.ToString("N");
} }
return series.PresentationUniqueKey; return series.GetPresentationUniqueKey();
} }
public override int GetChildCount(User user) public override int GetChildCount(User user)
@ -197,7 +207,30 @@ namespace MediaBrowser.Controller.Entities.TV
{ {
var config = user.Configuration; var config = user.Configuration;
return GetSeasons(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); var seriesKey = GetUniqueSeriesKey(this);
Logger.Debug("GetSeasons SeriesKey: {0}", seriesKey);
var query = new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] {typeof (Season).Name},
SortBy = new[] {ItemSortBy.SortName}
};
if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
{
query.IsVirtualItem = false;
}
else if (!config.DisplayMissingEpisodes)
{
query.IsMissing = false;
}
else if (!config.DisplayUnairedEpisodes)
{
query.IsVirtualUnaired = false;
}
return LibraryManager.GetItemList(query).Cast<Season>();
} }
protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query) protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
@ -227,55 +260,43 @@ namespace MediaBrowser.Controller.Entities.TV
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
var items = GetSeasons(user).Where(filter); var items = GetSeasons(user).Where(filter);
var result = PostFilterAndSort(items, query); var result = PostFilterAndSort(items, query, false, true);
return Task.FromResult(result); return Task.FromResult(result);
} }
public IEnumerable<Season> GetSeasons(User user, bool includeMissingSeasons, bool includeVirtualUnaired) public IEnumerable<Episode> GetEpisodes(User user)
{ {
IEnumerable<Season> seasons; var seriesKey = GetUniqueSeriesKey(this);
Logger.Debug("GetEpisodes seriesKey: {0}", seriesKey);
seasons = LibraryManager.GetItemList(new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), AncestorWithPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { typeof(Season).Name }, IncludeItemTypes = new[] {typeof (Episode).Name, typeof (Season).Name},
SortBy = new[] { ItemSortBy.SortName } SortBy = new[] {ItemSortBy.SortName}
};
}).Cast<Season>(); var config = user.Configuration;
if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
if (!includeMissingSeasons)
{ {
seasons = seasons.Where(i => !(i.IsMissingSeason)); query.IsVirtualItem = false;
} }
if (!includeVirtualUnaired) else if (!config.DisplayMissingEpisodes)
{ {
seasons = seasons.Where(i => !i.IsVirtualUnaired); query.IsMissing = false;
} }
else if (!config.DisplayUnairedEpisodes)
return seasons;
}
public IEnumerable<Episode> GetEpisodes(User user)
{
var config = user.Configuration;
return GetEpisodes(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
}
public IEnumerable<Episode> GetEpisodes(User user, bool includeMissing, bool includeVirtualUnaired)
{
var allItems = LibraryManager.GetItemList(new InternalItemsQuery(user)
{ {
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), query.IsVirtualUnaired = false;
IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name }, }
SortBy = new[] { ItemSortBy.SortName }
}).ToList(); var allItems = LibraryManager.GetItemList(query).ToList();
Logger.Debug("GetEpisodes return {0} items from database", allItems.Count);
var allSeriesEpisodes = allItems.OfType<Episode>().ToList(); var allSeriesEpisodes = allItems.OfType<Episode>().ToList();
var allEpisodes = allItems.OfType<Season>() var allEpisodes = allItems.OfType<Season>()
.SelectMany(i => i.GetEpisodes(this, user, includeMissing, includeVirtualUnaired, allSeriesEpisodes)) .SelectMany(i => i.GetEpisodes(this, user, allSeriesEpisodes))
.Reverse() .Reverse()
.ToList(); .ToList();
@ -352,78 +373,68 @@ namespace MediaBrowser.Controller.Entities.TV
progress.Report(100); progress.Report(100);
} }
public IEnumerable<Episode> GetEpisodes(User user, Season season)
{
var config = user.Configuration;
return GetEpisodes(user, season, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
}
private IEnumerable<Episode> GetAllEpisodes(User user) private IEnumerable<Episode> GetAllEpisodes(User user)
{ {
return LibraryManager.GetItemList(new InternalItemsQuery(user) Logger.Debug("Series.GetAllEpisodes entering GetItemList");
var result = LibraryManager.GetItemList(new InternalItemsQuery(user)
{ {
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
IncludeItemTypes = new[] { typeof(Episode).Name }, IncludeItemTypes = new[] { typeof(Episode).Name },
SortBy = new[] { ItemSortBy.SortName } SortBy = new[] { ItemSortBy.SortName }
}).Cast<Episode>(); }).Cast<Episode>().ToList();
}
public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) Logger.Debug("Series.GetAllEpisodes returning {0} episodes", result.Count);
{
IEnumerable<Episode> episodes = GetAllEpisodes(user);
return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes); return result;
} }
public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes) public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason)
{ {
if (allSeriesEpisodes == null) var seriesKey = GetUniqueSeriesKey(this);
Logger.Debug("GetSeasonEpisodes seriesKey: {0}", seriesKey);
var query = new InternalItemsQuery(user)
{ {
return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes); AncestorWithPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { typeof(Episode).Name },
SortBy = new[] { ItemSortBy.SortName }
};
var config = user.Configuration;
if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
{
query.IsVirtualItem = false;
} }
else if (!config.DisplayMissingEpisodes)
var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
if (!includeMissingEpisodes)
{ {
episodes = episodes.Where(i => !i.IsMissingEpisode); query.IsMissing = false;
} }
if (!includeVirtualUnairedEpisodes) else if (!config.DisplayUnairedEpisodes)
{ {
episodes = episodes.Where(i => !i.IsVirtualUnaired); query.IsVirtualUnaired = false;
} }
var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder; var allItems = LibraryManager.GetItemList(query).OfType<Episode>();
return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending) return GetSeasonEpisodes(user, parentSeason, allItems);
.Cast<Episode>();
} }
/// <summary> public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason, IEnumerable<Episode> allSeriesEpisodes)
/// Filters the episodes by season.
/// </summary>
public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
{ {
if (!includeSpecials || seasonNumber < 1) if (allSeriesEpisodes == null)
{ {
return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber); Logger.Debug("GetSeasonEpisodes allSeriesEpisodes is null");
return GetSeasonEpisodes(user, parentSeason);
} }
return episodes.Where(i => Logger.Debug("GetSeasonEpisodes FilterEpisodesBySeason");
{ var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
var episode = i;
if (episode != null)
{
var currentSeasonNumber = episode.AiredSeasonNumber;
return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber; var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
}
return false; return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending)
}); .Cast<Episode>();
} }
/// <summary> /// <summary>
@ -454,6 +465,32 @@ namespace MediaBrowser.Controller.Entities.TV
}); });
} }
/// <summary>
/// Filters the episodes by season.
/// </summary>
public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
{
if (!includeSpecials || seasonNumber < 1)
{
return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber);
}
return episodes.Where(i =>
{
var episode = i;
if (episode != null)
{
var currentSeasonNumber = episode.AiredSeasonNumber;
return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber;
}
return false;
});
}
protected override bool GetBlockUnratedValue(UserPolicy config) protected override bool GetBlockUnratedValue(UserPolicy config)
{ {
return config.BlockUnratedItems.Contains(UnratedItem.Series); return config.BlockUnratedItems.Contains(UnratedItem.Series);
@ -509,5 +546,15 @@ namespace MediaBrowser.Controller.Entities.TV
return list; return list;
} }
[IgnoreDataMember]
public override bool StopRefreshIfLocalMetadataFound
{
get
{
// Need people id's from internet metadata
return false;
}
}
} }
} }

@ -2,9 +2,7 @@
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
@ -126,5 +124,15 @@ namespace MediaBrowser.Controller.Entities
return list; return list;
} }
[IgnoreDataMember]
public override bool StopRefreshIfLocalMetadataFound
{
get
{
// Need people id's from internet metadata
return false;
}
}
} }
} }

@ -213,7 +213,7 @@ namespace MediaBrowser.Controller.Entities
Name = newName; Name = newName;
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)) return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem))
{ {
ReplaceAllMetadata = true, ReplaceAllMetadata = true,
ImageRefreshMode = ImageRefreshMode.FullRefresh, ImageRefreshMode = ImageRefreshMode.FullRefresh,

@ -1,6 +1,5 @@
using System.Runtime.Serialization; using System.Runtime.Serialization;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Library; using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using System; using System;
@ -8,7 +7,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {
@ -18,6 +16,31 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class UserRootFolder : Folder public class UserRootFolder : Folder
{ {
private List<Guid> _childrenIds = null;
private readonly object _childIdsLock = new object();
protected override IEnumerable<BaseItem> LoadChildren()
{
lock (_childIdsLock)
{
if (_childrenIds == null)
{
var list = base.LoadChildren().ToList();
_childrenIds = list.Select(i => i.Id).ToList();
return list;
}
return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
}
}
private void ClearCache()
{
lock (_childIdsLock)
{
_childrenIds = null;
}
}
protected override async Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query) protected override async Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{ {
if (query.Recursive) if (query.Recursive)
@ -35,7 +58,7 @@ namespace MediaBrowser.Controller.Entities
var user = query.User; var user = query.User;
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
return PostFilterAndSort(result.Where(filter), query); return PostFilterAndSort(result.Where(filter), query, true, true);
} }
public override int GetChildCount(User user) public override int GetChildCount(User user)
@ -71,6 +94,8 @@ namespace MediaBrowser.Controller.Entities
public override bool BeforeMetadataRefresh() public override bool BeforeMetadataRefresh()
{ {
ClearCache();
var hasChanges = base.BeforeMetadataRefresh(); var hasChanges = base.BeforeMetadataRefresh();
if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase)) if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase))
@ -82,11 +107,22 @@ namespace MediaBrowser.Controller.Entities
return hasChanges; return hasChanges;
} }
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
ClearCache();
return base.GetNonCachedChildren(directoryService);
}
protected override async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) protected override async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{ {
ClearCache();
await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService) await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService)
.ConfigureAwait(false); .ConfigureAwait(false);
ClearCache();
// Not the best way to handle this, but it solves an issue // Not the best way to handle this, but it solves an issue
// CollectionFolders aren't always getting saved after changes // CollectionFolders aren't always getting saved after changes
// This means that grabbing the item by Id may end up returning the old one // This means that grabbing the item by Id may end up returning the old one

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
@ -14,12 +13,10 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
using MoreLinq; using MoreLinq;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
@ -427,7 +424,7 @@ namespace MediaBrowser.Controller.Entities
query.SortBy = new string[] { }; query.SortBy = new string[] { };
return PostFilterAndSort(items, parent, null, query); return PostFilterAndSort(items, parent, null, query, false, true);
} }
private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query) private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query)
@ -783,7 +780,7 @@ namespace MediaBrowser.Controller.Entities
{ {
items = items.Where(i => Filter(i, query.User, query, _userDataManager, _libraryManager)); items = items.Where(i => Filter(i, query.User, query, _userDataManager, _libraryManager));
return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config); return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config, true, true);
} }
public static bool FilterItem(BaseItem item, InternalItemsQuery query) public static bool FilterItem(BaseItem item, InternalItemsQuery query)
@ -794,9 +791,11 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, private QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
BaseItem queryParent, BaseItem queryParent,
int? totalRecordLimit, int? totalRecordLimit,
InternalItemsQuery query) InternalItemsQuery query,
bool collapseBoxSetItems,
bool enableSorting)
{ {
return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config); return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config, collapseBoxSetItems, enableSorting);
} }
public static QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, public static QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
@ -804,7 +803,9 @@ namespace MediaBrowser.Controller.Entities
int? totalRecordLimit, int? totalRecordLimit,
InternalItemsQuery query, InternalItemsQuery query,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IServerConfigurationManager configurationManager) IServerConfigurationManager configurationManager,
bool collapseBoxSetItems,
bool enableSorting)
{ {
var user = query.User; var user = query.User;
@ -813,7 +814,10 @@ namespace MediaBrowser.Controller.Entities
query.IsVirtualUnaired, query.IsVirtualUnaired,
query.IsUnaired); query.IsUnaired);
items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager); if (collapseBoxSetItems)
{
items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager);
}
// This must be the last filter // This must be the last filter
if (!string.IsNullOrEmpty(query.AdjacentTo)) if (!string.IsNullOrEmpty(query.AdjacentTo))
@ -821,7 +825,7 @@ namespace MediaBrowser.Controller.Entities
items = FilterForAdjacency(items, query.AdjacentTo); items = FilterForAdjacency(items, query.AdjacentTo);
} }
return Sort(items, totalRecordLimit, query, libraryManager); return SortAndPage(items, totalRecordLimit, query, libraryManager, enableSorting);
} }
public static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items, public static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items,
@ -1096,8 +1100,6 @@ namespace MediaBrowser.Controller.Entities
bool? isVirtualUnaired, bool? isVirtualUnaired,
bool? isUnaired) bool? isUnaired)
{ {
items = FilterVirtualSeasons(items, isMissing, isVirtualUnaired, isUnaired);
if (isMissing.HasValue) if (isMissing.HasValue)
{ {
var val = isMissing.Value; var val = isMissing.Value;
@ -1143,65 +1145,14 @@ namespace MediaBrowser.Controller.Entities
return items; return items;
} }
private static IEnumerable<BaseItem> FilterVirtualSeasons( public static QueryResult<BaseItem> SortAndPage(IEnumerable<BaseItem> items,
IEnumerable<BaseItem> items,
bool? isMissing,
bool? isVirtualUnaired,
bool? isUnaired)
{
if (isMissing.HasValue)
{
var val = isMissing.Value;
items = items.Where(i =>
{
var e = i as Season;
if (e != null)
{
return (e.IsMissingSeason) == val;
}
return true;
});
}
if (isUnaired.HasValue)
{
var val = isUnaired.Value;
items = items.Where(i =>
{
var e = i as Season;
if (e != null)
{
return e.IsUnaired == val;
}
return true;
});
}
if (isVirtualUnaired.HasValue)
{
var val = isVirtualUnaired.Value;
items = items.Where(i =>
{
var e = i as Season;
if (e != null)
{
return e.IsVirtualUnaired == val;
}
return true;
});
}
return items;
}
public static QueryResult<BaseItem> Sort(IEnumerable<BaseItem> items,
int? totalRecordLimit, int? totalRecordLimit,
InternalItemsQuery query, InternalItemsQuery query,
ILibraryManager libraryManager) ILibraryManager libraryManager, bool enableSorting)
{ {
var user = query.User; var user = query.User;
items = items.DistinctBy(i => i.PresentationUniqueKey, StringComparer.OrdinalIgnoreCase); items = items.DistinctBy(i => i.GetPresentationUniqueKey(), StringComparer.OrdinalIgnoreCase);
if (query.SortBy.Length > 0) if (query.SortBy.Length > 0)
{ {

@ -12,6 +12,7 @@ using System.Runtime.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
@ -44,24 +45,23 @@ namespace MediaBrowser.Controller.Entities
} }
} }
[IgnoreDataMember] public override string CreatePresentationUniqueKey()
public override string PresentationUniqueKey
{ {
get if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
{ {
if (!string.IsNullOrWhiteSpace(PrimaryVersionId)) return PrimaryVersionId;
{
return PrimaryVersionId;
}
return base.PresentationUniqueKey;
} }
return base.CreatePresentationUniqueKey();
} }
[IgnoreDataMember] [IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange public override bool EnableRefreshOnDateModifiedChange
{ {
get { return true; } get
{
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso;
}
} }
public int? TotalBitrate { get; set; } public int? TotalBitrate { get; set; }
@ -612,6 +612,11 @@ namespace MediaBrowser.Controller.Entities
SupportsDirectStream = i.VideoType == VideoType.VideoFile SupportsDirectStream = i.VideoType == VideoType.VideoFile
}; };
if (info.Protocol == MediaProtocol.File)
{
info.ETag = i.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N");
}
if (i.IsShortcut) if (i.IsShortcut)
{ {
info.Path = i.ShortcutPath; info.Path = i.ShortcutPath;

@ -112,5 +112,48 @@ namespace MediaBrowser.Controller.Entities
return false; return false;
} }
} }
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.YearPath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
} }
} }

@ -1,5 +1,7 @@
using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.Events;
using MediaBrowser.Model.FileOrganization;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.FileOrganization
{ {
public interface IFileOrganizationService public interface IFileOrganizationService
{ {
event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemAdded;
event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemUpdated;
event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemRemoved;
event EventHandler LogReset;
/// <summary> /// <summary>
/// Processes the new files. /// Processes the new files.
/// </summary> /// </summary>
@ -81,5 +88,20 @@ namespace MediaBrowser.Controller.FileOrganization
/// <param name="ItemName">Item name.</param> /// <param name="ItemName">Item name.</param>
/// <param name="matchString">The match string to delete.</param> /// <param name="matchString">The match string to delete.</param>
void DeleteSmartMatchEntry(string ItemName, string matchString); void DeleteSmartMatchEntry(string ItemName, string matchString);
/// <summary>
/// Attempts to add a an item to the list of currently processed items.
/// </summary>
/// <param name="result">The result item.</param>
/// <param name="fullClientRefresh">Passing true will notify the client to reload all items, otherwise only a single item will be refreshed.</param>
/// <returns>True if the item was added, False if the item is already contained in the list.</returns>
bool AddToInProgressList(FileOrganizationResult result, bool fullClientRefresh);
/// <summary>
/// Removes an item from the list of currently processed items.
/// </summary>
/// <param name="result">The result item.</param>
/// <returns>True if the item was removed, False if the item was not contained in the list.</returns>
bool RemoveFromInprogressList(FileOrganizationResult result);
} }
} }

@ -106,5 +106,7 @@ namespace MediaBrowser.Controller
/// </summary> /// </summary>
/// <value>The internal metadata path.</value> /// <value>The internal metadata path.</value>
string InternalMetadataPath { get; } string InternalMetadataPath { get; }
string ArtistsPath { get; }
} }
} }

@ -11,6 +11,8 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Library namespace MediaBrowser.Controller.Library
@ -32,15 +34,11 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Resolves a set of files into a list of BaseItem /// Resolves a set of files into a list of BaseItem
/// </summary> /// </summary>
/// <param name="files">The files.</param>
/// <param name="directoryService">The directory service.</param>
/// <param name="parent">The parent.</param>
/// <param name="collectionType">Type of the collection.</param>
/// <returns>List{``0}.</returns>
IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
IDirectoryService directoryService, IDirectoryService directoryService,
Folder parent, string Folder parent,
collectionType = null); LibraryOptions libraryOptions,
string collectionType = null);
/// <summary> /// <summary>
/// Gets the root folder. /// Gets the root folder.
@ -397,6 +395,9 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if [is audio file] [the specified path]; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if [is audio file] [the specified path]; otherwise, <c>false</c>.</returns>
bool IsAudioFile(string path); bool IsAudioFile(string path);
bool IsAudioFile(string path, LibraryOptions libraryOptions);
bool IsVideoFile(string path, LibraryOptions libraryOptions);
/// <summary> /// <summary>
/// Gets the season number from path. /// Gets the season number from path.
/// </summary> /// </summary>
@ -453,6 +454,8 @@ namespace MediaBrowser.Controller.Library
/// <returns>IEnumerable&lt;Folder&gt;.</returns> /// <returns>IEnumerable&lt;Folder&gt;.</returns>
IEnumerable<Folder> GetCollectionFolders(BaseItem item); IEnumerable<Folder> GetCollectionFolders(BaseItem item);
LibraryOptions GetLibraryOptions(BaseItem item);
/// <summary> /// <summary>
/// Gets the people. /// Gets the people.
/// </summary> /// </summary>
@ -474,12 +477,6 @@ namespace MediaBrowser.Controller.Library
/// <returns>List&lt;Person&gt;.</returns> /// <returns>List&lt;Person&gt;.</returns>
List<Person> GetPeopleItems(InternalPeopleQuery query); List<Person> GetPeopleItems(InternalPeopleQuery query);
/// <summary>
/// Gets all people names.
/// </summary>
/// <returns>List&lt;System.String&gt;.</returns>
List<PersonInfo> GetAllPeople();
/// <summary> /// <summary>
/// Updates the people. /// Updates the people.
/// </summary> /// </summary>
@ -557,7 +554,7 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
bool IgnoreFile(FileSystemMetadata file, BaseItem parent); bool IgnoreFile(FileSystemMetadata file, BaseItem parent);
void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary); void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, LibraryOptions options, bool refreshLibrary);
void RemoveVirtualFolder(string name, bool refreshLibrary); void RemoveVirtualFolder(string name, bool refreshLibrary);
void AddMediaPath(string virtualFolderName, string path); void AddMediaPath(string virtualFolderName, string path);
void RemoveMediaPath(string virtualFolderName, string path); void RemoveMediaPath(string virtualFolderName, string path);
@ -568,5 +565,6 @@ namespace MediaBrowser.Controller.Library
QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query);
} }
} }

@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.Library namespace MediaBrowser.Controller.Library
{ {
@ -51,6 +53,13 @@ namespace MediaBrowser.Controller.Library
} }
} }
public LibraryOptions LibraryOptions { get; set; }
public LibraryOptions GetLibraryOptions()
{
return LibraryOptions ?? (LibraryOptions = (Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent)));
}
/// <summary> /// <summary>
/// Gets or sets the file system dictionary. /// Gets or sets the file system dictionary.
/// </summary> /// </summary>

@ -331,12 +331,11 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task AddInfoToProgramDto(List<Tuple<BaseItem,BaseItemDto>> programs, List<ItemFields> fields, User user = null); Task AddInfoToProgramDto(List<Tuple<BaseItem,BaseItemDto>> programs, List<ItemFields> fields, User user = null);
/// <summary> /// <summary>
/// Saves the tuner host. /// Saves the tuner host.
/// </summary> /// </summary>
/// <param name="info">The information.</param> Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
/// <returns>Task.</returns>
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info);
/// <summary> /// <summary>
/// Saves the listing provider. /// Saves the listing provider.
/// </summary> /// </summary>

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;

@ -1,10 +1,4 @@
using System; namespace MediaBrowser.Controller.LiveTv
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.LiveTv
{ {
public class TimerEventInfo public class TimerEventInfo
{ {

@ -1,10 +1,4 @@
using System; namespace MediaBrowser.Controller.LiveTv
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.LiveTv
{ {
public class TunerChannelMapping public class TunerChannelMapping
{ {

@ -236,6 +236,7 @@
<Compile Include="Net\IAuthorizationContext.cs" /> <Compile Include="Net\IAuthorizationContext.cs" />
<Compile Include="Net\IAuthService.cs" /> <Compile Include="Net\IAuthService.cs" />
<Compile Include="Net\IHasAuthorization.cs" /> <Compile Include="Net\IHasAuthorization.cs" />
<Compile Include="Net\IAsyncStreamSource.cs" />
<Compile Include="Net\IHasResultFactory.cs" /> <Compile Include="Net\IHasResultFactory.cs" />
<Compile Include="Net\IHasSession.cs" /> <Compile Include="Net\IHasSession.cs" />
<Compile Include="Net\IHttpResultFactory.cs" /> <Compile Include="Net\IHttpResultFactory.cs" />

@ -1,5 +1,4 @@
using System.Linq; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {

@ -1,7 +1,6 @@
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using System; using System;
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -134,5 +133,7 @@ namespace MediaBrowser.Controller.MediaEncoding
Task Init(); Task Init();
Task UpdateEncoderPath(string path, string pathType); Task UpdateEncoderPath(string path, string pathType);
bool SupportsEncoder(string encoder);
bool IsDefaultEncoderPath { get; }
} }
} }

@ -36,8 +36,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return new[] {videoPath}; return new[] {videoPath};
} }
public static List<string> GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, IEnumerable<string> filenames) private static List<string> GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, List<string> filenames)
{ {
if (filenames.Count == 0)
{
return new List<string>();
}
var allFiles = fileSystem var allFiles = fileSystem
.GetFilePaths(rootPath, true) .GetFilePaths(rootPath, true)
.ToList(); .ToList();

@ -0,0 +1,18 @@
using ServiceStack.Web;
using System.IO;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Net
{
/// <summary>
/// Interface IAsyncStreamSource
/// Enables asynchronous writing to http resonse streams
/// </summary>
public interface IAsyncStreamSource
{
/// <summary>
/// Asynchronously write to the response stream.
/// </summary>
Task WriteToAsync(Stream responseStream);
}
}

@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Net
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null); object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null);
object GetAsyncStreamWriter(Func<Stream,Task> streamWriter, IDictionary<string, string> responseHeaders = null); object GetAsyncStreamWriter(IAsyncStreamSource streamSource);
/// <summary> /// <summary>
/// Gets the optimized result. /// Gets the optimized result.

@ -169,6 +169,13 @@ namespace MediaBrowser.Controller.Persistence
QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query);
List<string> GetGameGenreNames();
List<string> GetMusicGenreNames();
List<string> GetStudioNames();
List<string> GetGenreNames();
List<string> GetAllArtistNames();
} }
} }

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Controller.Playlists namespace MediaBrowser.Controller.Playlists
{ {
@ -58,11 +59,22 @@ namespace MediaBrowser.Controller.Playlists
return true; return true;
} }
protected override IEnumerable<BaseItem> LoadChildren()
{
// Save a trip to the database
return new List<BaseItem>();
}
public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren) public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
{ {
return GetPlayableItems(user).Result; return GetPlayableItems(user).Result;
} }
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
return new List<BaseItem>();
}
public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query) public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
{ {
var items = GetPlayableItems(user).Result; var items = GetPlayableItems(user).Result;

@ -724,6 +724,15 @@ namespace MediaBrowser.Controller.Providers
} }
break; break;
} }
case "TvMazeId":
{
var id = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(id))
{
item.SetProviderId(MetadataProviders.TvMaze, id);
}
break;
}
case "AudioDbArtistId": case "AudioDbArtistId":
{ {
var id = reader.ReadElementContentAsString(); var id = reader.ReadElementContentAsString();

@ -16,13 +16,16 @@ namespace MediaBrowser.Controller.Providers
private readonly ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>> _cache = private readonly ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>> _cache =
new ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>>(StringComparer.OrdinalIgnoreCase); new ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>>(StringComparer.OrdinalIgnoreCase);
public DirectoryService(ILogger logger, IFileSystem fileSystem) private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache =
new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
public DirectoryService(ILogger logger, IFileSystem fileSystem)
{ {
_logger = logger; _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
} }
public DirectoryService(IFileSystem fileSystem) public DirectoryService(IFileSystem fileSystem)
: this(new NullLogger(), fileSystem) : this(new NullLogger(), fileSystem)
{ {
} }
@ -100,20 +103,19 @@ namespace MediaBrowser.Controller.Providers
public FileSystemMetadata GetFile(string path) public FileSystemMetadata GetFile(string path)
{ {
var directory = Path.GetDirectoryName(path); FileSystemMetadata file;
if (!_fileCache.TryGetValue(path, out file))
if (string.IsNullOrWhiteSpace(directory))
{ {
_logger.Debug("Parent path is null for {0}", path); file = _fileSystem.GetFileInfo(path);
return null;
}
var dict = GetFileSystemDictionary(directory, false);
FileSystemMetadata entry; if (file != null)
dict.TryGetValue(path, out entry); {
_fileCache.TryAdd(path, file);
}
}
return entry; return file;
//return _fileSystem.GetFileInfo(path);
} }
public IEnumerable<FileSystemMetadata> GetDirectories(string path) public IEnumerable<FileSystemMetadata> GetDirectories(string path)

@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using CommonIO; using CommonIO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Providers namespace MediaBrowser.Controller.Providers
@ -19,7 +20,7 @@ namespace MediaBrowser.Controller.Providers
public bool ForceSave { get; set; } public bool ForceSave { get; set; }
public MetadataRefreshOptions(IFileSystem fileSystem) public MetadataRefreshOptions(IFileSystem fileSystem)
: this(new DirectoryService(fileSystem)) : this(new DirectoryService(new NullLogger(), fileSystem))
{ {
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save