commit
0780889c51
|
@ -192,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;
|
||||||
|
|
||||||
|
@ -208,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;
|
||||||
|
@ -219,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,
|
||||||
|
@ -694,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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -1055,14 +1060,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");
|
||||||
|
@ -1112,28 +1117,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);
|
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
|
||||||
state.TranscodingThrottler.Start();
|
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)
|
||||||
|
@ -1171,6 +1178,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(' ');
|
||||||
|
|
||||||
|
@ -1234,11 +1242,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1588,6 +1617,10 @@ namespace MediaBrowser.Api.Playback
|
||||||
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (i == 29)
|
||||||
|
{
|
||||||
|
request.Tag = val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2192,6 +2225,121 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (!string.Equals(MediaEncoder.EncoderLocationType, "Default", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
|
|
@ -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) ?
|
||||||
|
|
|
@ -154,12 +154,20 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
using (state)
|
using (state)
|
||||||
{
|
{
|
||||||
|
TimeSpan? cacheDuration = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.Tag))
|
||||||
|
{
|
||||||
|
cacheDuration = TimeSpan.FromDays(365);
|
||||||
|
}
|
||||||
|
|
||||||
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 = state.MediaPath
|
Path = state.MediaPath,
|
||||||
|
CacheDuration = cacheDuration
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -362,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
|
||||||
{
|
{
|
||||||
|
@ -383,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;
|
||||||
|
|
|
@ -4,38 +4,55 @@ 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 = 81920;
|
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
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _outputHeaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteToAsync(Stream outputStream)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var eofCount = 0;
|
var eofCount = 0;
|
||||||
|
|
||||||
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||||
{
|
{
|
||||||
while (eofCount < 15)
|
while (eofCount < 15)
|
||||||
{
|
{
|
||||||
var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, cancellationToken).ConfigureAwait(false);
|
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);
|
||||||
|
@ -46,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
{
|
{
|
||||||
eofCount++;
|
eofCount++;
|
||||||
}
|
}
|
||||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
await Task.Delay(100, _cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace MediaBrowser.Api.Sync
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (item.IsFolder && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
|
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 +44,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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -214,6 +214,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>
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,24 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
item = user == null ? _libraryManager.RootFolder : user.RootFolder;
|
item = user == null ? _libraryManager.RootFolder : user.RootFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.Ids))
|
||||||
|
{
|
||||||
|
var query = GetItemsQuery(request, user);
|
||||||
|
var specificItems = _libraryManager.GetItemList(query).ToArray();
|
||||||
|
if (query.SortBy.Length == 0)
|
||||||
|
{
|
||||||
|
var ids = query.ItemIds.ToList();
|
||||||
|
|
||||||
|
// Try to preserve order
|
||||||
|
specificItems = specificItems.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
|
||||||
|
}
|
||||||
|
return new QueryResult<BaseItem>
|
||||||
|
{
|
||||||
|
Items = specificItems.ToArray(),
|
||||||
|
TotalRecordCount = specificItems.Length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Default list type = children
|
// Default list type = children
|
||||||
|
|
||||||
var folder = item as Folder;
|
var folder = item as Folder;
|
||||||
|
@ -157,33 +175,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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -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,
|
||||||
|
@ -136,6 +167,21 @@ 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,14 +169,10 @@ 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)
|
||||||
{
|
{
|
||||||
return config.BlockUnratedItems.Contains(UnratedItem.Music);
|
return config.BlockUnratedItems.Contains(UnratedItem.Music);
|
||||||
|
@ -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,14 +18,10 @@ 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]
|
||||||
public override bool SupportsAddingToPlaylist
|
public override bool SupportsAddingToPlaylist
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,7 +455,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; }
|
||||||
}
|
}
|
||||||
|
@ -951,7 +951,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 +981,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 =>
|
||||||
{
|
{
|
||||||
|
@ -1194,10 +1194,17 @@ namespace MediaBrowser.Controller.Entities
|
||||||
get { return null; }
|
get { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
public virtual string CreatePresentationUniqueKey()
|
||||||
public virtual string PresentationUniqueKey
|
|
||||||
{
|
{
|
||||||
get { return Id.ToString("N"); }
|
return Id.ToString("N");
|
||||||
|
}
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public string PresentationUniqueKey { get; set; }
|
||||||
|
|
||||||
|
public string GetPresentationUniqueKey()
|
||||||
|
{
|
||||||
|
return PresentationUniqueKey ?? CreatePresentationUniqueKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool RequiresRefresh()
|
public virtual bool RequiresRefresh()
|
||||||
|
@ -2206,6 +2213,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,71 @@ 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;
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
|
@ -13,6 +13,7 @@ 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.Controller.Entities.TV;
|
||||||
using MediaBrowser.Model.Channels;
|
using MediaBrowser.Model.Channels;
|
||||||
|
|
||||||
|
@ -273,6 +274,7 @@ 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();
|
||||||
}
|
}
|
||||||
|
@ -643,8 +645,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 +702,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))
|
||||||
|
@ -900,7 +903,15 @@ 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 specificItems = query.ItemIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
|
||||||
return Task.FromResult(PostFilterAndSort(specificItems, query));
|
|
||||||
|
if (query.SortBy.Length == 0)
|
||||||
|
{
|
||||||
|
var ids = query.ItemIds.ToList();
|
||||||
|
|
||||||
|
// Try to preserve order
|
||||||
|
specificItems = specificItems.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToList();
|
||||||
|
}
|
||||||
|
return Task.FromResult(PostFilterAndSort(specificItems, query, true, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetItemsInternal(query);
|
return GetItemsInternal(query);
|
||||||
|
@ -956,12 +967,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)
|
||||||
|
@ -1425,7 +1436,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)
|
||||||
|
@ -1435,6 +1446,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,13 +16,10 @@ 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>
|
||||||
/// Returns the folder containing the item.
|
/// Returns the folder containing the item.
|
||||||
|
@ -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,14 +19,10 @@ 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>
|
||||||
/// Returns the folder containing the item.
|
/// Returns the folder containing the item.
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,12 @@ 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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -75,9 +95,23 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||||
protected override bool SupportsShortcutChildren
|
protected override bool SupportsShortcutChildren
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
{
|
||||||
|
if (IsLegacyBoxSet)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
private bool IsLegacyBoxSet
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsAuthorizedToDelete(User user)
|
public override bool IsAuthorizedToDelete(User user)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,14 +26,10 @@ 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,14 +18,10 @@ 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>
|
||||||
/// Returns the folder containing the item.
|
/// Returns the folder containing the item.
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,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>
|
||||||
|
@ -114,10 +118,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
public override string CreatePresentationUniqueKey()
|
||||||
public override string PresentationUniqueKey
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
if (IndexNumber.HasValue)
|
if (IndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -128,8 +129,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.PresentationUniqueKey;
|
return base.CreatePresentationUniqueKey();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -141,24 +141,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)
|
||||||
|
@ -170,10 +152,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,19 +171,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()
|
||||||
|
|
|
@ -96,19 +96,29 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
public override string CreatePresentationUniqueKey()
|
||||||
public override string PresentationUniqueKey
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
var userdatakeys = GetUserDataKeys();
|
var userdatakeys = GetUserDataKeys();
|
||||||
|
|
||||||
if (userdatakeys.Count > 1)
|
if (userdatakeys.Count > 1)
|
||||||
{
|
{
|
||||||
return userdatakeys[0];
|
return AddLibrariesToPresentationUniqueKey(userdatakeys[0]);
|
||||||
}
|
}
|
||||||
return base.PresentationUniqueKey;
|
return base.CreatePresentationUniqueKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string AddLibrariesToPresentationUniqueKey(string key)
|
||||||
|
{
|
||||||
|
var folders = LibraryManager.GetCollectionFolders(this)
|
||||||
|
.Select(i => i.Id.ToString("N"))
|
||||||
|
.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)
|
|
||||||
{
|
|
||||||
IEnumerable<Season> seasons;
|
|
||||||
|
|
||||||
seasons = LibraryManager.GetItemList(new InternalItemsQuery(user)
|
|
||||||
{
|
|
||||||
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
|
|
||||||
IncludeItemTypes = new[] { typeof(Season).Name },
|
|
||||||
SortBy = new[] { ItemSortBy.SortName }
|
|
||||||
|
|
||||||
}).Cast<Season>();
|
|
||||||
|
|
||||||
if (!includeMissingSeasons)
|
|
||||||
{
|
|
||||||
seasons = seasons.Where(i => !(i.IsMissingSeason));
|
|
||||||
}
|
|
||||||
if (!includeVirtualUnaired)
|
|
||||||
{
|
|
||||||
seasons = seasons.Where(i => !i.IsVirtualUnaired);
|
|
||||||
}
|
|
||||||
|
|
||||||
return seasons;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Episode> GetEpisodes(User user)
|
public IEnumerable<Episode> GetEpisodes(User user)
|
||||||
{
|
{
|
||||||
var config = user.Configuration;
|
var seriesKey = GetUniqueSeriesKey(this);
|
||||||
|
Logger.Debug("GetEpisodes seriesKey: {0}", seriesKey);
|
||||||
|
|
||||||
return GetEpisodes(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
|
var query = new InternalItemsQuery(user)
|
||||||
|
{
|
||||||
|
AncestorWithPresentationUniqueKey = seriesKey,
|
||||||
|
IncludeItemTypes = new[] {typeof (Episode).Name, typeof (Season).Name},
|
||||||
|
SortBy = new[] {ItemSortBy.SortName}
|
||||||
|
};
|
||||||
|
var config = user.Configuration;
|
||||||
|
if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
|
||||||
|
{
|
||||||
|
query.IsVirtualItem = false;
|
||||||
|
}
|
||||||
|
else if (!config.DisplayMissingEpisodes)
|
||||||
|
{
|
||||||
|
query.IsMissing = false;
|
||||||
|
}
|
||||||
|
else if (!config.DisplayUnairedEpisodes)
|
||||||
|
{
|
||||||
|
query.IsVirtualUnaired = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Episode> GetEpisodes(User user, bool includeMissing, bool includeVirtualUnaired)
|
var allItems = LibraryManager.GetItemList(query).ToList();
|
||||||
{
|
|
||||||
var allItems = LibraryManager.GetItemList(new InternalItemsQuery(user)
|
|
||||||
{
|
|
||||||
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
|
|
||||||
IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name },
|
|
||||||
SortBy = new[] { ItemSortBy.SortName }
|
|
||||||
|
|
||||||
}).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,80 +373,70 @@ 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();
|
||||||
|
|
||||||
|
Logger.Debug("Series.GetAllEpisodes returning {0} episodes", result.Count);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
|
public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason)
|
||||||
{
|
{
|
||||||
IEnumerable<Episode> episodes = GetAllEpisodes(user);
|
var seriesKey = GetUniqueSeriesKey(this);
|
||||||
|
Logger.Debug("GetSeasonEpisodes seriesKey: {0}", seriesKey);
|
||||||
|
|
||||||
return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
|
var query = new InternalItemsQuery(user)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
query.IsMissing = false;
|
||||||
|
}
|
||||||
|
else if (!config.DisplayUnairedEpisodes)
|
||||||
|
{
|
||||||
|
query.IsVirtualUnaired = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
|
var allItems = LibraryManager.GetItemList(query).OfType<Episode>();
|
||||||
|
|
||||||
|
return GetSeasonEpisodes(user, parentSeason, allItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason, IEnumerable<Episode> allSeriesEpisodes)
|
||||||
{
|
{
|
||||||
if (allSeriesEpisodes == null)
|
if (allSeriesEpisodes == null)
|
||||||
{
|
{
|
||||||
return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes);
|
Logger.Debug("GetSeasonEpisodes allSeriesEpisodes is null");
|
||||||
|
return GetSeasonEpisodes(user, parentSeason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Debug("GetSeasonEpisodes FilterEpisodesBySeason");
|
||||||
var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
|
var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
|
||||||
|
|
||||||
if (!includeMissingEpisodes)
|
|
||||||
{
|
|
||||||
episodes = episodes.Where(i => !i.IsMissingEpisode);
|
|
||||||
}
|
|
||||||
if (!includeVirtualUnairedEpisodes)
|
|
||||||
{
|
|
||||||
episodes = episodes.Where(i => !i.IsVirtualUnaired);
|
|
||||||
}
|
|
||||||
|
|
||||||
var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
|
var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
|
||||||
|
|
||||||
return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending)
|
return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending)
|
||||||
.Cast<Episode>();
|
.Cast<Episode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Filters the episodes by season.
|
/// Filters the episodes by season.
|
||||||
/// </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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,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)
|
||||||
|
@ -33,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)
|
||||||
|
@ -69,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))
|
||||||
|
@ -80,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
|
||||||
|
|
|
@ -424,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)
|
||||||
|
@ -780,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)
|
||||||
|
@ -791,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,
|
||||||
|
@ -801,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;
|
||||||
|
|
||||||
|
@ -810,7 +814,10 @@ namespace MediaBrowser.Controller.Entities
|
||||||
query.IsVirtualUnaired,
|
query.IsVirtualUnaired,
|
||||||
query.IsUnaired);
|
query.IsUnaired);
|
||||||
|
|
||||||
|
if (collapseBoxSetItems)
|
||||||
|
{
|
||||||
items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager);
|
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))
|
||||||
|
@ -818,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,
|
||||||
|
@ -1093,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;
|
||||||
|
@ -1140,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Folder>.</returns>
|
/// <returns>IEnumerable<Folder>.</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>
|
||||||
|
@ -551,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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
18
MediaBrowser.Controller/Net/IAsyncStreamSource.cs
Normal file
18
MediaBrowser.Controller/Net/IAsyncStreamSource.cs
Normal file
|
@ -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.
|
||||||
|
|
|
@ -170,6 +170,12 @@ namespace MediaBrowser.Controller.Persistence
|
||||||
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);
|
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;
|
||||||
|
|
|
@ -73,8 +73,13 @@ namespace MediaBrowser.Dlna
|
||||||
lock (_profiles)
|
lock (_profiles)
|
||||||
{
|
{
|
||||||
var list = _profiles.Values.ToList();
|
var list = _profiles.Values.ToList();
|
||||||
return list.Select(i => i.Item2).OrderBy(i => i.Name);
|
return list
|
||||||
|
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
|
||||||
|
.ThenBy(i => i.Item1.Info.Name)
|
||||||
|
.Select(i => i.Item2)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceProfile GetDefaultProfile()
|
public DeviceProfile GetDefaultProfile()
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -91,6 +92,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
public DateTime DateLastActivity { get; private set; }
|
public DateTime DateLastActivity { get; private set; }
|
||||||
|
public Action OnDeviceUnavailable { get; set; }
|
||||||
|
|
||||||
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
|
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
|
||||||
{
|
{
|
||||||
|
@ -134,6 +136,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
|
|
||||||
private async void RefreshVolume()
|
private async void RefreshVolume()
|
||||||
{
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await GetVolume().ConfigureAwait(false);
|
await GetVolume().ConfigureAwait(false);
|
||||||
|
@ -149,6 +154,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
private bool _timerActive;
|
private bool _timerActive;
|
||||||
private void RestartTimer()
|
private void RestartTimer()
|
||||||
{
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!_timerActive)
|
if (!_timerActive)
|
||||||
{
|
{
|
||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
|
@ -169,6 +177,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void RestartTimerInactive()
|
private void RestartTimerInactive()
|
||||||
{
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
if (_timerActive)
|
if (_timerActive)
|
||||||
{
|
{
|
||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
|
@ -398,6 +409,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
#region Get data
|
#region Get data
|
||||||
|
|
||||||
private int _successiveStopCount;
|
private int _successiveStopCount;
|
||||||
|
private int _connectFailureCount;
|
||||||
private async void TimerCallback(object sender)
|
private async void TimerCallback(object sender)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
@ -435,6 +447,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_connectFailureCount = 0;
|
||||||
|
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -455,8 +469,33 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_logger.ErrorException("Error updating device info for {0}", ex, Properties.Name);
|
||||||
|
|
||||||
|
_successiveStopCount++;
|
||||||
|
_connectFailureCount++;
|
||||||
|
|
||||||
|
if (_successiveStopCount >= maxSuccessiveStopReturns)
|
||||||
|
{
|
||||||
|
RestartTimerInactive();
|
||||||
|
}
|
||||||
|
if (_connectFailureCount >= maxSuccessiveStopReturns)
|
||||||
|
{
|
||||||
|
if (OnDeviceUnavailable != null)
|
||||||
|
{
|
||||||
|
OnDeviceUnavailable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
_logger.ErrorException("Error updating device info for {0}", ex, Properties.Name);
|
_logger.ErrorException("Error updating device info for {0}", ex, Properties.Name);
|
||||||
|
|
||||||
_successiveStopCount++;
|
_successiveStopCount++;
|
||||||
|
|
|
@ -103,11 +103,25 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_device.PlaybackProgress += _device_PlaybackProgress;
|
_device.PlaybackProgress += _device_PlaybackProgress;
|
||||||
_device.PlaybackStopped += _device_PlaybackStopped;
|
_device.PlaybackStopped += _device_PlaybackStopped;
|
||||||
_device.MediaChanged += _device_MediaChanged;
|
_device.MediaChanged += _device_MediaChanged;
|
||||||
|
_device.OnDeviceUnavailable = OnDeviceUnavailable;
|
||||||
|
|
||||||
_device.Start();
|
_device.Start();
|
||||||
|
|
||||||
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
|
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDeviceUnavailable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_sessionManager.ReportSessionEnded(_session.Id);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Could throw if the session is already gone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
|
void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
|
||||||
{
|
{
|
||||||
string nts;
|
string nts;
|
||||||
|
@ -125,14 +139,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
|
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||||
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)
|
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
{
|
{
|
||||||
try
|
OnDeviceUnavailable();
|
||||||
{
|
|
||||||
_sessionManager.ReportSessionEnded(_session.Id);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Could throw if the session is already gone
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -647,6 +654,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
_device.PlaybackStopped -= _device_PlaybackStopped;
|
||||||
_device.MediaChanged -= _device_MediaChanged;
|
_device.MediaChanged -= _device_MediaChanged;
|
||||||
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
||||||
|
_device.OnDeviceUnavailable = null;
|
||||||
|
|
||||||
_device.Dispose();
|
_device.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,14 +65,26 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
{
|
{
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
{
|
{
|
||||||
Container = "mp3,wma",
|
Container = "m4v,ts,mkv,avi,mpg,mpeg,mp4",
|
||||||
Type = DlnaProfileType.Audio
|
VideoCodec = "h264",
|
||||||
|
AudioCodec = "aac,mp3,ac3",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
{
|
{
|
||||||
Container = "avi,mp4",
|
Container = "mp3,wma,aac,wav",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Audio
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new[]
|
||||||
|
{
|
||||||
|
new ResponseProfile
|
||||||
|
{
|
||||||
|
Container = "m4v",
|
||||||
|
Type = DlnaProfileType.Video,
|
||||||
|
MimeType = "video/mp4"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Type = DlnaProfileType.Audio
|
Type = DlnaProfileType.Audio
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,8 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Type = DlnaProfileType.Audio
|
Type = DlnaProfileType.Audio
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,6 +198,8 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Method = SubtitleDeliveryMethod.External
|
Method = SubtitleDeliveryMethod.External
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,8 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Type = DlnaProfileType.Audio
|
Type = DlnaProfileType.Audio
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,6 +200,8 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,6 +181,8 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,8 @@
|
||||||
<IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
|
<IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
|
||||||
<XmlRootAttributes />
|
<XmlRootAttributes />
|
||||||
<DirectPlayProfiles>
|
<DirectPlayProfiles>
|
||||||
<DirectPlayProfile container="mp3,wma" type="Audio" />
|
<DirectPlayProfile container="m4v,ts,mkv,avi,mpg,mpeg,mp4" audioCodec="aac,mp3,ac3" videoCodec="h264" type="Video" />
|
||||||
<DirectPlayProfile container="avi,mp4" type="Video" />
|
<DirectPlayProfile container="mp3,wma,aac,wav" type="Audio" />
|
||||||
</DirectPlayProfiles>
|
</DirectPlayProfiles>
|
||||||
<TranscodingProfiles>
|
<TranscodingProfiles>
|
||||||
<TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" enableSubtitlesInManifest="false" />
|
<TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" enableSubtitlesInManifest="false" />
|
||||||
|
@ -39,6 +39,10 @@
|
||||||
</TranscodingProfiles>
|
</TranscodingProfiles>
|
||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles>
|
||||||
|
<ResponseProfile container="m4v" type="Video" mimeType="video/mp4">
|
||||||
|
<Conditions />
|
||||||
|
</ResponseProfile>
|
||||||
|
</ResponseProfiles>
|
||||||
<SubtitleProfiles />
|
<SubtitleProfiles />
|
||||||
</Profile>
|
</Profile>
|
|
@ -51,7 +51,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
var metadata = string.Empty;
|
var metadata = string.Empty;
|
||||||
var vn = string.Empty;
|
var vn = string.Empty;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(state.AlbumCoverPath))
|
var hasArt = !string.IsNullOrWhiteSpace(state.AlbumCoverPath);
|
||||||
|
hasArt = false;
|
||||||
|
|
||||||
|
if (hasArt)
|
||||||
{
|
{
|
||||||
albumCoverInput = " -i \"" + state.AlbumCoverPath + "\"";
|
albumCoverInput = " -i \"" + state.AlbumCoverPath + "\"";
|
||||||
mapArgs = " -map 0:a -map 1:v -c:v copy";
|
mapArgs = " -map 0:a -map 1:v -c:v copy";
|
||||||
|
|
|
@ -123,10 +123,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return "System";
|
return "System";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsDefaultPath(FFMpegPath))
|
||||||
|
{
|
||||||
|
return "Default";
|
||||||
|
}
|
||||||
|
|
||||||
return "Custom";
|
return "Custom";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsDefaultPath(string path)
|
||||||
|
{
|
||||||
|
var parentPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg", "20160410");
|
||||||
|
|
||||||
|
return FileSystem.ContainsSubPath(parentPath, path);
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsSystemInstalledPath(string path)
|
private bool IsSystemInstalledPath(string path)
|
||||||
{
|
{
|
||||||
if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1)
|
if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1)
|
||||||
|
@ -343,14 +355,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
// If that doesn't pan out, then do a recursive search
|
// If that doesn't pan out, then do a recursive search
|
||||||
var files = Directory.GetFiles(path);
|
var files = Directory.GetFiles(path);
|
||||||
|
|
||||||
var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase));
|
var excludeExtensions = new[] { ".c" };
|
||||||
var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
|
||||||
|
var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath))
|
if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath))
|
||||||
{
|
{
|
||||||
files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
|
files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
|
||||||
|
|
||||||
ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase));
|
ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(ffmpegPath))
|
if (!string.IsNullOrWhiteSpace(ffmpegPath))
|
||||||
{
|
{
|
||||||
|
@ -874,8 +888,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
|
var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
|
||||||
|
|
||||||
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
|
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
|
||||||
var args = useIFrame ? string.Format("-i {0}{3} -threads 1 -v quiet -vframes 1 -vf \"{2},thumbnail=30\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg) :
|
var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=30\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg) :
|
||||||
string.Format("-i {0}{3} -threads 1 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
|
string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
|
||||||
|
|
||||||
var probeSize = GetProbeSizeArgument(new[] { inputPath }, protocol);
|
var probeSize = GetProbeSizeArgument(new[] { inputPath }, protocol);
|
||||||
|
|
||||||
|
@ -980,7 +994,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
FileSystem.CreateDirectory(targetDirectory);
|
FileSystem.CreateDirectory(targetDirectory);
|
||||||
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
|
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
|
||||||
|
|
||||||
var args = string.Format("-i {0} -threads 1 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
|
var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
|
||||||
|
|
||||||
var probeSize = GetProbeSizeArgument(new[] { inputArgument }, protocol);
|
var probeSize = GetProbeSizeArgument(new[] { inputArgument }, protocol);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
|
||||||
|
@ -793,7 +794,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
if (!string.IsNullOrWhiteSpace(artists))
|
if (!string.IsNullOrWhiteSpace(artists))
|
||||||
{
|
{
|
||||||
audio.Artists = SplitArtists(artists, new[] { '/', ';' }, false)
|
audio.Artists = SplitArtists(artists, new[] { '/', ';' }, false)
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.DistinctNames()
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -806,7 +807,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
audio.Artists = SplitArtists(artist, _nameDelimiters, true)
|
audio.Artists = SplitArtists(artist, _nameDelimiters, true)
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.DistinctNames()
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -828,7 +829,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true)
|
audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true)
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.DistinctNames()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,6 +205,9 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\ImageSavingConvention.cs">
|
<Compile Include="..\MediaBrowser.Model\Configuration\ImageSavingConvention.cs">
|
||||||
<Link>Configuration\ImageSavingConvention.cs</Link>
|
<Link>Configuration\ImageSavingConvention.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Configuration\LibraryOptions.cs">
|
||||||
|
<Link>Configuration\LibraryOptions.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\MetadataConfiguration.cs">
|
<Compile Include="..\MediaBrowser.Model\Configuration\MetadataConfiguration.cs">
|
||||||
<Link>Configuration\MetadataConfiguration.cs</Link>
|
<Link>Configuration\MetadataConfiguration.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -177,6 +177,9 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\ImageSavingConvention.cs">
|
<Compile Include="..\MediaBrowser.Model\Configuration\ImageSavingConvention.cs">
|
||||||
<Link>Configuration\ImageSavingConvention.cs</Link>
|
<Link>Configuration\ImageSavingConvention.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Configuration\LibraryOptions.cs">
|
||||||
|
<Link>Configuration\LibraryOptions.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Configuration\MetadataConfiguration.cs">
|
<Compile Include="..\MediaBrowser.Model\Configuration\MetadataConfiguration.cs">
|
||||||
<Link>Configuration\MetadataConfiguration.cs</Link>
|
<Link>Configuration\MetadataConfiguration.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
13
MediaBrowser.Model/Configuration/LibraryOptions.cs
Normal file
13
MediaBrowser.Model/Configuration/LibraryOptions.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
namespace MediaBrowser.Model.Configuration
|
||||||
|
{
|
||||||
|
public class LibraryOptions
|
||||||
|
{
|
||||||
|
public bool EnableArchiveMediaFiles { get; set; }
|
||||||
|
public bool EnablePhotos { get; set; }
|
||||||
|
|
||||||
|
public LibraryOptions()
|
||||||
|
{
|
||||||
|
EnablePhotos = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -180,8 +180,6 @@ namespace MediaBrowser.Model.Configuration
|
||||||
|
|
||||||
public NameValuePair[] ContentTypes { get; set; }
|
public NameValuePair[] ContentTypes { get; set; }
|
||||||
|
|
||||||
public bool EnableAudioArchiveFiles { get; set; }
|
|
||||||
public bool EnableVideoArchiveFiles { get; set; }
|
|
||||||
public int RemoteClientBitrateLimit { get; set; }
|
public int RemoteClientBitrateLimit { get; set; }
|
||||||
|
|
||||||
public AutoOnOff EnableLibraryMonitor { get; set; }
|
public AutoOnOff EnableLibraryMonitor { get; set; }
|
||||||
|
@ -204,6 +202,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
public bool DisplaySpecialsWithinSeasons { get; set; }
|
public bool DisplaySpecialsWithinSeasons { get; set; }
|
||||||
public bool DisplayCollectionsView { get; set; }
|
public bool DisplayCollectionsView { get; set; }
|
||||||
public string[] LocalNetworkAddresses { get; set; }
|
public string[] LocalNetworkAddresses { get; set; }
|
||||||
|
public string[] CodecsUsed { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
|
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
|
||||||
|
@ -212,6 +211,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
{
|
{
|
||||||
LocalNetworkAddresses = new string[] { };
|
LocalNetworkAddresses = new string[] { };
|
||||||
Migrations = new string[] { };
|
Migrations = new string[] { };
|
||||||
|
CodecsUsed = new string[] { };
|
||||||
SqliteCacheSize = 0;
|
SqliteCacheSize = 0;
|
||||||
|
|
||||||
EnableLocalizedGuids = true;
|
EnableLocalizedGuids = true;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using MediaBrowser.Model.Extensions;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
|
@ -59,8 +60,8 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
private static double GetVideoBitrateScaleFactor(string codec)
|
private static double GetVideoBitrateScaleFactor(string codec)
|
||||||
{
|
{
|
||||||
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) ||
|
if (StringHelper.EqualsIgnoreCase(codec, "h265") ||
|
||||||
string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
|
StringHelper.EqualsIgnoreCase(codec, "hevc"))
|
||||||
{
|
{
|
||||||
return .5;
|
return .5;
|
||||||
}
|
}
|
||||||
|
|
|
@ -570,7 +570,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue);
|
playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(), playlistItem.TargetAudioChannels, playlistItem.TargetAudioCodec, audioStream);
|
int audioBitrate = GetAudioBitrate(playlistItem.SubProtocol, options.GetMaxBitrate(), playlistItem.TargetAudioChannels, playlistItem.TargetAudioCodec, audioStream);
|
||||||
playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate);
|
playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate);
|
||||||
|
|
||||||
int? maxBitrateSetting = options.GetMaxBitrate();
|
int? maxBitrateSetting = options.GetMaxBitrate();
|
||||||
|
@ -593,7 +593,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return playlistItem;
|
return playlistItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetAudioBitrate(int? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream)
|
private int GetAudioBitrate(string subProtocol, int? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream)
|
||||||
{
|
{
|
||||||
var defaultBitrate = 128000;
|
var defaultBitrate = 128000;
|
||||||
if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3"))
|
if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3"))
|
||||||
|
@ -610,9 +610,16 @@ namespace MediaBrowser.Model.Dlna
|
||||||
if (targetAudioChannels.Value >= 5 && (maxTotalBitrate ?? 0) >= 1500000)
|
if (targetAudioChannels.Value >= 5 && (maxTotalBitrate ?? 0) >= 1500000)
|
||||||
{
|
{
|
||||||
if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3"))
|
if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3"))
|
||||||
|
{
|
||||||
|
if (string.Equals(subProtocol, "hls", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
defaultBitrate = Math.Max(384000, defaultBitrate);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
defaultBitrate = Math.Max(448000, defaultBitrate);
|
defaultBitrate = Math.Max(448000, defaultBitrate);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
defaultBitrate = Math.Max(320000, defaultBitrate);
|
defaultBitrate = Math.Max(320000, defaultBitrate);
|
||||||
|
|
|
@ -252,6 +252,8 @@ namespace MediaBrowser.Model.Dlna
|
||||||
list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.TranscodingMaxAudioChannels.Value) : string.Empty));
|
list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.TranscodingMaxAudioChannels.Value) : string.Empty));
|
||||||
list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString().ToLower()));
|
list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString().ToLower()));
|
||||||
|
|
||||||
|
list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty));
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -346,7 +346,16 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// Gets or sets a value indicating whether this instance is folder.
|
/// Gets or sets a value indicating whether this instance is folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
|
||||||
public bool IsFolder { get; set; }
|
public bool? IsFolder { get; set; }
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public bool IsFolderItem
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return IsFolder ?? false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the parent id.
|
/// Gets or sets the parent id.
|
||||||
|
@ -656,7 +665,7 @@ namespace MediaBrowser.Model.Dto
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return RunTimeTicks.HasValue || IsFolder || IsGenre || IsMusicGenre || IsArtist;
|
return RunTimeTicks.HasValue || IsFolderItem || IsGenre || IsMusicGenre || IsArtist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -837,6 +846,7 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The album count.</value>
|
/// <value>The album count.</value>
|
||||||
public int? AlbumCount { get; set; }
|
public int? AlbumCount { get; set; }
|
||||||
|
public int? ArtistCount { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the music video count.
|
/// Gets or sets the music video count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The game count.</value>
|
/// <value>The game count.</value>
|
||||||
public int GameCount { get; set; }
|
public int GameCount { get; set; }
|
||||||
|
public int ArtistCount { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the game system count.
|
/// Gets or sets the game system count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace MediaBrowser.Model.Dto
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string ETag { get; set; }
|
||||||
public long? RunTimeTicks { get; set; }
|
public long? RunTimeTicks { get; set; }
|
||||||
public bool ReadAtNativeFramerate { get; set; }
|
public bool ReadAtNativeFramerate { get; set; }
|
||||||
public bool SupportsTranscoding { get; set; }
|
public bool SupportsTranscoding { get; set; }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Entities
|
namespace MediaBrowser.Model.Entities
|
||||||
{
|
{
|
||||||
|
@ -25,6 +26,8 @@ namespace MediaBrowser.Model.Entities
|
||||||
/// <value>The type of the collection.</value>
|
/// <value>The type of the collection.</value>
|
||||||
public string CollectionType { get; set; }
|
public string CollectionType { get; set; }
|
||||||
|
|
||||||
|
public LibraryOptions LibraryOptions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="VirtualFolderInfo"/> class.
|
/// Initializes a new instance of the <see cref="VirtualFolderInfo"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -59,5 +59,11 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if [add current program]; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if [add current program]; otherwise, <c>false</c>.</value>
|
||||||
public bool AddCurrentProgram { get; set; }
|
public bool AddCurrentProgram { get; set; }
|
||||||
|
public bool EnableUserData { get; set; }
|
||||||
|
|
||||||
|
public LiveTvChannelQuery()
|
||||||
|
{
|
||||||
|
EnableUserData = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,11 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
SortBy = new string[] { };
|
SortBy = new string[] { };
|
||||||
Genres = new string[] { };
|
Genres = new string[] { };
|
||||||
EnableTotalRecordCount = true;
|
EnableTotalRecordCount = true;
|
||||||
|
EnableUserData = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EnableTotalRecordCount { get; set; }
|
public bool EnableTotalRecordCount { get; set; }
|
||||||
|
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
|
||||||
|
|
|
@ -95,6 +95,7 @@
|
||||||
<Compile Include="Configuration\CinemaModeConfiguration.cs" />
|
<Compile Include="Configuration\CinemaModeConfiguration.cs" />
|
||||||
<Compile Include="Configuration\EncodingOptions.cs" />
|
<Compile Include="Configuration\EncodingOptions.cs" />
|
||||||
<Compile Include="Configuration\FanartOptions.cs" />
|
<Compile Include="Configuration\FanartOptions.cs" />
|
||||||
|
<Compile Include="Configuration\LibraryOptions.cs" />
|
||||||
<Compile Include="Configuration\MetadataConfiguration.cs" />
|
<Compile Include="Configuration\MetadataConfiguration.cs" />
|
||||||
<Compile Include="Configuration\PeopleMetadataOptions.cs" />
|
<Compile Include="Configuration\PeopleMetadataOptions.cs" />
|
||||||
<Compile Include="Configuration\XbmcMetadataOptions.cs" />
|
<Compile Include="Configuration\XbmcMetadataOptions.cs" />
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace MediaBrowser.Model.Sync
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The user identifier.</value>
|
/// <value>The user identifier.</value>
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
|
public string ExcludeTargetIds { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the status.
|
/// Gets or sets the status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -41,6 +41,7 @@ namespace MediaBrowser.Model.Users
|
||||||
public bool EnableMediaPlayback { get; set; }
|
public bool EnableMediaPlayback { get; set; }
|
||||||
public bool EnableAudioPlaybackTranscoding { get; set; }
|
public bool EnableAudioPlaybackTranscoding { get; set; }
|
||||||
public bool EnableVideoPlaybackTranscoding { get; set; }
|
public bool EnableVideoPlaybackTranscoding { get; set; }
|
||||||
|
public bool EnablePlaybackRemuxing { get; set; }
|
||||||
|
|
||||||
public bool EnableContentDeletion { get; set; }
|
public bool EnableContentDeletion { get; set; }
|
||||||
public bool EnableContentDownloading { get; set; }
|
public bool EnableContentDownloading { get; set; }
|
||||||
|
@ -76,6 +77,7 @@ namespace MediaBrowser.Model.Users
|
||||||
EnableMediaPlayback = true;
|
EnableMediaPlayback = true;
|
||||||
EnableAudioPlaybackTranscoding = true;
|
EnableAudioPlaybackTranscoding = true;
|
||||||
EnableVideoPlaybackTranscoding = true;
|
EnableVideoPlaybackTranscoding = true;
|
||||||
|
EnablePlaybackRemuxing = true;
|
||||||
|
|
||||||
EnableLiveTvManagement = true;
|
EnableLiveTvManagement = true;
|
||||||
EnableLiveTvAccess = true;
|
EnableLiveTvAccess = true;
|
||||||
|
|
|
@ -42,6 +42,13 @@ namespace MediaBrowser.Providers.Manager
|
||||||
var config = ProviderManager.GetMetadataOptions(item);
|
var config = ProviderManager.GetMetadataOptions(item);
|
||||||
|
|
||||||
var updateType = ItemUpdateType.None;
|
var updateType = ItemUpdateType.None;
|
||||||
|
var requiresRefresh = false;
|
||||||
|
|
||||||
|
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
|
||||||
|
{
|
||||||
|
// TODO: If this returns true, should we instead just change metadata refresh mode to Full?
|
||||||
|
requiresRefresh = item.RequiresRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem);
|
var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem);
|
||||||
var localImagesFailed = false;
|
var localImagesFailed = false;
|
||||||
|
@ -70,14 +77,10 @@ namespace MediaBrowser.Providers.Manager
|
||||||
|
|
||||||
bool hasRefreshedMetadata = true;
|
bool hasRefreshedMetadata = true;
|
||||||
bool hasRefreshedImages = true;
|
bool hasRefreshedImages = true;
|
||||||
var requiresRefresh = false;
|
|
||||||
|
|
||||||
// Next run metadata providers
|
// Next run metadata providers
|
||||||
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
|
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
|
||||||
{
|
{
|
||||||
// TODO: If this returns true, should we instead just change metadata refresh mode to Full?
|
|
||||||
requiresRefresh = item.RequiresRefresh();
|
|
||||||
|
|
||||||
var providers = GetProviders(item, refreshOptions, requiresRefresh)
|
var providers = GetProviders(item, refreshOptions, requiresRefresh)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
@ -149,7 +152,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
if (file != null)
|
if (file != null)
|
||||||
{
|
{
|
||||||
var fileLastWriteTime = file.LastWriteTimeUtc;
|
var fileLastWriteTime = file.LastWriteTimeUtc;
|
||||||
if (item.EnableForceSaveOnDateModifiedChange && fileLastWriteTime != item.DateModified)
|
if (item.EnableRefreshOnDateModifiedChange && fileLastWriteTime != item.DateModified)
|
||||||
{
|
{
|
||||||
Logger.Debug("Date modified for {0}. Old date {1} new date {2} Id {3}", item.Path, item.DateModified, fileLastWriteTime, item.Id);
|
Logger.Debug("Date modified for {0}. Old date {1} new date {2} Id {3}", item.Path, item.DateModified, fileLastWriteTime, item.Id);
|
||||||
requiresRefresh = true;
|
requiresRefresh = true;
|
||||||
|
@ -284,6 +287,13 @@ namespace MediaBrowser.Providers.Manager
|
||||||
updateType |= SaveCumulativeRunTimeTicks(item, isFullRefresh, currentUpdateType);
|
updateType |= SaveCumulativeRunTimeTicks(item, isFullRefresh, currentUpdateType);
|
||||||
updateType |= SaveDateLastMediaAdded(item, isFullRefresh, currentUpdateType);
|
updateType |= SaveDateLastMediaAdded(item, isFullRefresh, currentUpdateType);
|
||||||
|
|
||||||
|
var presentationUniqueKey = item.CreatePresentationUniqueKey();
|
||||||
|
if (!string.Equals(item.PresentationUniqueKey, presentationUniqueKey, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
item.PresentationUniqueKey = presentationUniqueKey;
|
||||||
|
updateType |= ItemUpdateType.MetadataImport;
|
||||||
|
}
|
||||||
|
|
||||||
return updateType;
|
return updateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,7 +535,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local metadata is king - if any is found don't run remote providers
|
// Local metadata is king - if any is found don't run remote providers
|
||||||
if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh))
|
if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || !item.StopRefreshIfLocalMetadataFound))
|
||||||
{
|
{
|
||||||
var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
|
var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
|
@ -170,12 +170,15 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
|
public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
|
||||||
|
{
|
||||||
|
if (item.EnableRefreshOnDateModifiedChange && !string.IsNullOrWhiteSpace(item.Path))
|
||||||
{
|
{
|
||||||
var file = directoryService.GetFile(item.Path);
|
var file = directoryService.GetFile(item.Path);
|
||||||
if (file != null && file.LastWriteTimeUtc != item.DateModified)
|
if (file != null && file.LastWriteTimeUtc != item.DateModified)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (item.SupportsLocalMetadata)
|
if (item.SupportsLocalMetadata)
|
||||||
{
|
{
|
||||||
|
|
|
@ -193,12 +193,15 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
|
public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
|
||||||
|
{
|
||||||
|
if (item.EnableRefreshOnDateModifiedChange)
|
||||||
{
|
{
|
||||||
var file = directoryService.GetFile(item.Path);
|
var file = directoryService.GetFile(item.Path);
|
||||||
if (file != null && file.LastWriteTimeUtc != item.DateModified)
|
if (file != null && file.LastWriteTimeUtc != item.DateModified)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,46 +81,24 @@ namespace MediaBrowser.Providers.Music
|
||||||
|
|
||||||
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(XmlDocument doc)
|
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(XmlDocument doc)
|
||||||
{
|
{
|
||||||
var ns = new XmlNamespaceManager(doc.NameTable);
|
return ReleaseResult.Parse(doc).Select(i =>
|
||||||
ns.AddNamespace("mb", MusicBrainzBaseUrl + "/ns/mmd-2.0#");
|
|
||||||
|
|
||||||
var list = new List<RemoteSearchResult>();
|
|
||||||
|
|
||||||
var nodes = doc.SelectNodes("//mb:release-list/mb:release", ns);
|
|
||||||
|
|
||||||
if (nodes != null)
|
|
||||||
{
|
|
||||||
foreach (var node in nodes.Cast<XmlNode>())
|
|
||||||
{
|
|
||||||
if (node.Attributes != null)
|
|
||||||
{
|
|
||||||
string name = null;
|
|
||||||
|
|
||||||
string mbzId = node.Attributes["id"].Value;
|
|
||||||
|
|
||||||
var nameNode = node.SelectSingleNode("//mb:title", ns);
|
|
||||||
|
|
||||||
if (nameNode != null)
|
|
||||||
{
|
|
||||||
name = nameNode.InnerText;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(mbzId) && !string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
{
|
||||||
var result = new RemoteSearchResult
|
var result = new RemoteSearchResult
|
||||||
{
|
{
|
||||||
Name = name
|
Name = i.Title
|
||||||
};
|
};
|
||||||
|
|
||||||
result.SetProviderId(MetadataProviders.MusicBrainzAlbum, mbzId);
|
if (!string.IsNullOrWhiteSpace(i.ReleaseId))
|
||||||
|
{
|
||||||
list.Add(result);
|
result.SetProviderId(MetadataProviders.MusicBrainzAlbum, i.ReleaseId);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId))
|
||||||
|
{
|
||||||
|
result.SetProviderId(MetadataProviders.MusicBrainzAlbum, i.ReleaseGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return result;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo id, CancellationToken cancellationToken)
|
public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo id, CancellationToken cancellationToken)
|
||||||
|
@ -208,7 +186,7 @@ namespace MediaBrowser.Providers.Music
|
||||||
|
|
||||||
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
|
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return ReleaseResult.Parse(doc);
|
return ReleaseResult.Parse(doc, 1).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ReleaseResult> GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
|
private async Task<ReleaseResult> GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
|
||||||
|
@ -219,32 +197,32 @@ namespace MediaBrowser.Providers.Music
|
||||||
|
|
||||||
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
|
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return ReleaseResult.Parse(doc);
|
return ReleaseResult.Parse(doc, 1).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReleaseResult
|
private class ReleaseResult
|
||||||
{
|
{
|
||||||
public string ReleaseId;
|
public string ReleaseId;
|
||||||
public string ReleaseGroupId;
|
public string ReleaseGroupId;
|
||||||
|
public string Title;
|
||||||
|
|
||||||
public static ReleaseResult Parse(XmlDocument doc)
|
public static List<ReleaseResult> Parse(XmlDocument doc, int? limit = null)
|
||||||
{
|
{
|
||||||
var docElem = doc.DocumentElement;
|
var docElem = doc.DocumentElement;
|
||||||
|
var list = new List<ReleaseResult>();
|
||||||
|
|
||||||
if (docElem == null)
|
if (docElem == null)
|
||||||
{
|
{
|
||||||
return new ReleaseResult();
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
var releaseList = docElem.FirstChild;
|
var releaseList = docElem.FirstChild;
|
||||||
if (releaseList == null)
|
if (releaseList == null)
|
||||||
{
|
{
|
||||||
return new ReleaseResult();
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodes = releaseList.ChildNodes;
|
var nodes = releaseList.ChildNodes;
|
||||||
string releaseId = null;
|
|
||||||
string releaseGroupId = null;
|
|
||||||
|
|
||||||
if (nodes != null)
|
if (nodes != null)
|
||||||
{
|
{
|
||||||
|
@ -252,18 +230,42 @@ namespace MediaBrowser.Providers.Music
|
||||||
{
|
{
|
||||||
if (string.Equals(node.Name, "release", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(node.Name, "release", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
releaseId = node.Attributes["id"].Value;
|
var releaseId = node.Attributes["id"].Value;
|
||||||
releaseGroupId = GetReleaseGroupIdFromReleaseNode(node);
|
var releaseGroupId = GetReleaseGroupIdFromReleaseNode(node);
|
||||||
|
|
||||||
|
list.Add(new ReleaseResult
|
||||||
|
{
|
||||||
|
ReleaseId = releaseId,
|
||||||
|
ReleaseGroupId = releaseGroupId,
|
||||||
|
Title = GetTitleFromReleaseNode(node)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (limit.HasValue && list.Count >= limit.Value)
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new ReleaseResult
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetTitleFromReleaseNode(XmlNode node)
|
||||||
{
|
{
|
||||||
ReleaseId = releaseId,
|
var subNodes = node.ChildNodes;
|
||||||
ReleaseGroupId = releaseGroupId
|
if (subNodes != null)
|
||||||
};
|
{
|
||||||
|
foreach (var subNode in subNodes.Cast<XmlNode>())
|
||||||
|
{
|
||||||
|
if (string.Equals(subNode.Name, "title", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return subNode.InnerText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetReleaseGroupIdFromReleaseNode(XmlNode node)
|
private static string GetReleaseGroupIdFromReleaseNode(XmlNode node)
|
||||||
|
|
|
@ -112,7 +112,9 @@ namespace MediaBrowser.Providers.TV
|
||||||
IndexNumber = seasonNumber,
|
IndexNumber = seasonNumber,
|
||||||
Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)),
|
Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)),
|
||||||
IsVirtualItem = isVirtualItem,
|
IsVirtualItem = isVirtualItem,
|
||||||
SeriesId = series.Id
|
SeriesId = series.Id,
|
||||||
|
SeriesName = series.Name,
|
||||||
|
SeriesSortName = series.SortName
|
||||||
};
|
};
|
||||||
|
|
||||||
season.SetParent(series);
|
season.SetParent(series);
|
||||||
|
|
|
@ -35,27 +35,18 @@ namespace MediaBrowser.Providers.TV
|
||||||
updateType |= SaveIsVirtualItem(item, episodes);
|
updateType |= SaveIsVirtualItem(item, episodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateType <= ItemUpdateType.None)
|
|
||||||
{
|
|
||||||
if (!string.Equals(item.SeriesName, item.FindSeriesName(), StringComparison.Ordinal))
|
if (!string.Equals(item.SeriesName, item.FindSeriesName(), StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
updateType |= ItemUpdateType.MetadataImport;
|
updateType |= ItemUpdateType.MetadataImport;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (updateType <= ItemUpdateType.None)
|
|
||||||
{
|
|
||||||
if (!string.Equals(item.SeriesSortName, item.FindSeriesSortName(), StringComparison.Ordinal))
|
if (!string.Equals(item.SeriesSortName, item.FindSeriesSortName(), StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
updateType |= ItemUpdateType.MetadataImport;
|
updateType |= ItemUpdateType.MetadataImport;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (updateType <= ItemUpdateType.None)
|
|
||||||
{
|
|
||||||
if (item.SeriesId != item.FindSeriesId())
|
if (item.SeriesId != item.FindSeriesId())
|
||||||
{
|
{
|
||||||
updateType |= ItemUpdateType.MetadataImport;
|
updateType |= ItemUpdateType.MetadataImport;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return updateType;
|
return updateType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace MediaBrowser.Server.Implementations.Collections
|
||||||
public class CollectionsDynamicFolder : IVirtualFolderCreator
|
public class CollectionsDynamicFolder : IVirtualFolderCreator
|
||||||
{
|
{
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
public CollectionsDynamicFolder(IApplicationPaths appPaths, IFileSystem fileSystem)
|
public CollectionsDynamicFolder(IApplicationPaths appPaths, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
|
|
|
@ -329,7 +329,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await AttachUserSpecificInfo(dto, item, user, fields).ConfigureAwait(false);
|
await AttachUserSpecificInfo(dto, item, user, options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasMediaSources = item as IHasMediaSources;
|
var hasMediaSources = item as IHasMediaSources;
|
||||||
|
@ -408,12 +408,19 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
|
|
||||||
private void SetItemByNameInfo(BaseItem item, BaseItemDto dto, List<BaseItem> taggedItems, User user = null)
|
private void SetItemByNameInfo(BaseItem item, BaseItemDto dto, List<BaseItem> taggedItems, User user = null)
|
||||||
{
|
{
|
||||||
if (item is MusicArtist || item is MusicGenre)
|
if (item is MusicArtist)
|
||||||
{
|
{
|
||||||
dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
|
dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
|
||||||
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
|
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
|
||||||
dto.SongCount = taggedItems.Count(i => i is Audio);
|
dto.SongCount = taggedItems.Count(i => i is Audio);
|
||||||
}
|
}
|
||||||
|
else if (item is MusicGenre)
|
||||||
|
{
|
||||||
|
dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
|
||||||
|
dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
|
||||||
|
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
|
||||||
|
dto.SongCount = taggedItems.Count(i => i is Audio);
|
||||||
|
}
|
||||||
else if (item is GameGenre)
|
else if (item is GameGenre)
|
||||||
{
|
{
|
||||||
dto.GameCount = taggedItems.Count(i => i is Game);
|
dto.GameCount = taggedItems.Count(i => i is Game);
|
||||||
|
@ -422,6 +429,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
// This populates them all and covers Genre, Person, Studio, Year
|
// This populates them all and covers Genre, Person, Studio, Year
|
||||||
|
|
||||||
|
dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
|
||||||
dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
|
dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
|
||||||
dto.EpisodeCount = taggedItems.Count(i => i is Episode);
|
dto.EpisodeCount = taggedItems.Count(i => i is Episode);
|
||||||
dto.GameCount = taggedItems.Count(i => i is Game);
|
dto.GameCount = taggedItems.Count(i => i is Game);
|
||||||
|
@ -438,19 +446,20 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attaches the user specific info.
|
/// Attaches the user specific info.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dto">The dto.</param>
|
private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions dtoOptions)
|
||||||
/// <param name="item">The item.</param>
|
|
||||||
/// <param name="user">The user.</param>
|
|
||||||
/// <param name="fields">The fields.</param>
|
|
||||||
private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields)
|
|
||||||
{
|
{
|
||||||
|
var fields = dtoOptions.Fields;
|
||||||
|
|
||||||
if (item.IsFolder)
|
if (item.IsFolder)
|
||||||
{
|
{
|
||||||
var folder = (Folder)item;
|
var folder = (Folder)item;
|
||||||
|
|
||||||
|
if (dtoOptions.EnableUserData)
|
||||||
|
{
|
||||||
dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user).ConfigureAwait(false);
|
dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (item.SourceType == SourceType.Library)
|
if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
|
||||||
{
|
{
|
||||||
dto.ChildCount = GetChildCount(folder, user);
|
dto.ChildCount = GetChildCount(folder, user);
|
||||||
}
|
}
|
||||||
|
@ -467,16 +476,22 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (dtoOptions.EnableUserData)
|
||||||
{
|
{
|
||||||
dto.UserData = _userDataRepository.GetUserDataDto(item, user).Result;
|
dto.UserData = _userDataRepository.GetUserDataDto(item, user).Result;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dto.PlayAccess = item.GetPlayAccess(user);
|
dto.PlayAccess = item.GetPlayAccess(user);
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.BasicSyncInfo) || fields.Contains(ItemFields.SyncInfo))
|
if (fields.Contains(ItemFields.BasicSyncInfo) || fields.Contains(ItemFields.SyncInfo))
|
||||||
{
|
{
|
||||||
var userCanSync = user != null && user.Policy.EnableSync;
|
var userCanSync = user != null && user.Policy.EnableSync;
|
||||||
dto.SupportsSync = userCanSync && _syncManager.SupportsSync(item);
|
if (userCanSync && _syncManager.SupportsSync(item))
|
||||||
|
{
|
||||||
|
dto.SupportsSync = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.SeasonUserData))
|
if (fields.Contains(ItemFields.SeasonUserData))
|
||||||
|
@ -940,6 +955,8 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
dto.Genres = item.Genres;
|
dto.Genres = item.Genres;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.EnableImages)
|
||||||
|
{
|
||||||
dto.ImageTags = new Dictionary<ImageType, string>();
|
dto.ImageTags = new Dictionary<ImageType, string>();
|
||||||
|
|
||||||
// Prevent implicitly captured closure
|
// Prevent implicitly captured closure
|
||||||
|
@ -957,11 +974,21 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dto.Id = GetDtoId(item);
|
dto.Id = GetDtoId(item);
|
||||||
dto.IndexNumber = item.IndexNumber;
|
dto.IndexNumber = item.IndexNumber;
|
||||||
dto.ParentIndexNumber = item.ParentIndexNumber;
|
dto.ParentIndexNumber = item.ParentIndexNumber;
|
||||||
dto.IsFolder = item.IsFolder;
|
|
||||||
|
if (item.IsFolder)
|
||||||
|
{
|
||||||
|
dto.IsFolder = true;
|
||||||
|
}
|
||||||
|
else if (item is IHasMediaSources)
|
||||||
|
{
|
||||||
|
dto.IsFolder = false;
|
||||||
|
}
|
||||||
|
|
||||||
dto.MediaType = item.MediaType;
|
dto.MediaType = item.MediaType;
|
||||||
dto.LocationType = item.LocationType;
|
dto.LocationType = item.LocationType;
|
||||||
if (item.IsHD.HasValue && item.IsHD.Value)
|
if (item.IsHD.HasValue && item.IsHD.Value)
|
||||||
|
@ -1503,97 +1530,6 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Since it can be slow to make all of these calculations independently, this method will provide a way to do them all at once
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="folder">The folder.</param>
|
|
||||||
/// <param name="user">The user.</param>
|
|
||||||
/// <param name="dto">The dto.</param>
|
|
||||||
/// <param name="fields">The fields.</param>
|
|
||||||
/// <param name="syncProgress">The synchronize progress.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
private async Task SetSpecialCounts(Folder folder, User user, BaseItemDto dto, List<ItemFields> fields, Dictionary<string, SyncJobItemStatus> syncProgress)
|
|
||||||
{
|
|
||||||
var recursiveItemCount = 0;
|
|
||||||
var unplayed = 0;
|
|
||||||
|
|
||||||
double totalPercentPlayed = 0;
|
|
||||||
double totalSyncPercent = 0;
|
|
||||||
|
|
||||||
var children = await folder.GetItems(new InternalItemsQuery
|
|
||||||
{
|
|
||||||
IsFolder = false,
|
|
||||||
Recursive = true,
|
|
||||||
ExcludeLocationTypes = new[] { LocationType.Virtual },
|
|
||||||
User = user
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// Loop through each recursive child
|
|
||||||
foreach (var child in children.Items)
|
|
||||||
{
|
|
||||||
var userdata = _userDataRepository.GetUserData(user, child);
|
|
||||||
|
|
||||||
recursiveItemCount++;
|
|
||||||
|
|
||||||
var isUnplayed = true;
|
|
||||||
|
|
||||||
// Incrememt totalPercentPlayed
|
|
||||||
if (userdata != null)
|
|
||||||
{
|
|
||||||
if (userdata.Played)
|
|
||||||
{
|
|
||||||
totalPercentPlayed += 100;
|
|
||||||
|
|
||||||
isUnplayed = false;
|
|
||||||
}
|
|
||||||
else if (userdata.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0)
|
|
||||||
{
|
|
||||||
double itemPercent = userdata.PlaybackPositionTicks;
|
|
||||||
itemPercent /= child.RunTimeTicks.Value;
|
|
||||||
totalPercentPlayed += itemPercent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUnplayed)
|
|
||||||
{
|
|
||||||
unplayed++;
|
|
||||||
}
|
|
||||||
|
|
||||||
double percent = 0;
|
|
||||||
SyncJobItemStatus syncItemProgress;
|
|
||||||
if (syncProgress.TryGetValue(child.Id.ToString("N"), out syncItemProgress))
|
|
||||||
{
|
|
||||||
switch (syncItemProgress)
|
|
||||||
{
|
|
||||||
case SyncJobItemStatus.Synced:
|
|
||||||
percent = 100;
|
|
||||||
break;
|
|
||||||
case SyncJobItemStatus.Converting:
|
|
||||||
case SyncJobItemStatus.ReadyToTransfer:
|
|
||||||
case SyncJobItemStatus.Transferring:
|
|
||||||
percent = 50;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
totalSyncPercent += percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
dto.RecursiveItemCount = recursiveItemCount;
|
|
||||||
dto.UserData.UnplayedItemCount = unplayed;
|
|
||||||
|
|
||||||
if (recursiveItemCount > 0)
|
|
||||||
{
|
|
||||||
dto.UserData.PlayedPercentage = totalPercentPlayed / recursiveItemCount;
|
|
||||||
|
|
||||||
var pct = totalSyncPercent / recursiveItemCount;
|
|
||||||
if (pct > 0)
|
|
||||||
{
|
|
||||||
dto.SyncPercent = pct;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attaches the primary image aspect ratio.
|
/// Attaches the primary image aspect ratio.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -43,13 +43,6 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<FileOrganizationResult> OrganizeEpisodeFile(string path, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var options = _config.GetAutoOrganizeOptions();
|
|
||||||
|
|
||||||
return OrganizeEpisodeFile(path, options, false, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken)
|
public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.Info("Sorting file {0}", path);
|
_logger.Info("Sorting file {0}", path);
|
||||||
|
@ -63,6 +56,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
FileSize = new FileInfo(path).Length
|
FileSize = new FileInfo(path).Length
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
if (_libraryMonitor.IsPathLocked(path))
|
if (_libraryMonitor.IsPathLocked(path))
|
||||||
{
|
{
|
||||||
result.Status = FileSortingStatus.Failure;
|
result.Status = FileSortingStatus.Failure;
|
||||||
|
@ -148,6 +143,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
}
|
}
|
||||||
|
|
||||||
await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
|
await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Status = FileSortingStatus.Failure;
|
||||||
|
result.StatusMessage = ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -156,6 +157,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
{
|
{
|
||||||
var result = _organizationService.GetResult(request.ResultId);
|
var result = _organizationService.GetResult(request.ResultId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
Series series = null;
|
Series series = null;
|
||||||
|
|
||||||
if (request.NewSeriesProviderIds.Count > 0)
|
if (request.NewSeriesProviderIds.Count > 0)
|
||||||
|
@ -207,6 +210,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
|
await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Status = FileSortingStatus.Failure;
|
||||||
|
result.StatusMessage = ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -263,16 +272,15 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
|
|
||||||
var originalExtractedSeriesString = result.ExtractedName;
|
var originalExtractedSeriesString = result.ExtractedName;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
// Proceed to sort the file
|
// Proceed to sort the file
|
||||||
var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false);
|
var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(newPath))
|
if (string.IsNullOrEmpty(newPath))
|
||||||
{
|
{
|
||||||
var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath);
|
var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath);
|
||||||
result.Status = FileSortingStatus.Failure;
|
throw new Exception(msg);
|
||||||
result.StatusMessage = msg;
|
|
||||||
_logger.Warn(msg);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath);
|
_logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath);
|
||||||
|
@ -347,6 +355,14 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Status = FileSortingStatus.Failure;
|
||||||
|
result.StatusMessage = ex.Message;
|
||||||
|
_logger.Warn(ex.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (rememberCorrection)
|
if (rememberCorrection)
|
||||||
{
|
{
|
||||||
|
@ -505,7 +521,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var errorMsg = string.Format("Failed to move file from {0} to {1}", result.OriginalPath, result.TargetPath);
|
var errorMsg = string.Format("Failed to move file from {0} to {1}: {2}", result.OriginalPath, result.TargetPath, ex.Message);
|
||||||
|
|
||||||
result.Status = FileSortingStatus.Failure;
|
result.Status = FileSortingStatus.Failure;
|
||||||
result.StatusMessage = errorMsg;
|
result.StatusMessage = errorMsg;
|
||||||
|
@ -616,7 +632,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
{
|
{
|
||||||
var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
|
var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
|
||||||
_logger.Warn(msg);
|
_logger.Warn(msg);
|
||||||
return null;
|
throw new Exception(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeName = episode.Name;
|
var episodeName = episode.Name;
|
||||||
|
@ -715,6 +731,11 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
|
|
||||||
var pattern = endingEpisodeNumber.HasValue ? options.MultiEpisodeNamePattern : options.EpisodeNamePattern;
|
var pattern = endingEpisodeNumber.HasValue ? options.MultiEpisodeNamePattern : options.EpisodeNamePattern;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(pattern))
|
||||||
|
{
|
||||||
|
throw new Exception("GetEpisodeFileName: Configured episode name pattern is empty!");
|
||||||
|
}
|
||||||
|
|
||||||
var result = pattern.Replace("%sn", seriesName)
|
var result = pattern.Replace("%sn", seriesName)
|
||||||
.Replace("%s.n", seriesName.Replace(" ", "."))
|
.Replace("%s.n", seriesName.Replace(" ", "."))
|
||||||
.Replace("%s_n", seriesName.Replace(" ", "_"))
|
.Replace("%s_n", seriesName.Replace(" ", "_"))
|
||||||
|
@ -759,8 +780,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
// There may be cases where reducing the title length may still not be sufficient to
|
// There may be cases where reducing the title length may still not be sufficient to
|
||||||
// stay below maxLength
|
// stay below maxLength
|
||||||
var msg = string.Format("Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit", maxLength);
|
var msg = string.Format("Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit", maxLength);
|
||||||
_logger.Warn(msg);
|
throw new Exception(msg);
|
||||||
return string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -112,8 +112,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
|
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
|
||||||
_libraryMonitor, _providerManager);
|
_libraryMonitor, _providerManager);
|
||||||
|
|
||||||
await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeOptions(), true, CancellationToken.None)
|
var organizeResult = await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeOptions(), true, CancellationToken.None)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (organizeResult.Status != FileSortingStatus.Success)
|
||||||
|
{
|
||||||
|
throw new Exception(result.StatusMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ClearLog()
|
public Task ClearLog()
|
||||||
|
@ -126,7 +131,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
|
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
|
||||||
_libraryMonitor, _providerManager);
|
_libraryMonitor, _providerManager);
|
||||||
|
|
||||||
await organizer.OrganizeWithCorrection(request, GetAutoOrganizeOptions(), CancellationToken.None).ConfigureAwait(false);
|
var result = await organizer.OrganizeWithCorrection(request, GetAutoOrganizeOptions(), CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (result.Status != FileSortingStatus.Success)
|
||||||
|
{
|
||||||
|
throw new Exception(result.StatusMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query)
|
public QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query)
|
||||||
|
|
|
@ -4,38 +4,41 @@ using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using ServiceStack.Web;
|
using ServiceStack.Web;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.HttpServer
|
namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
public class AsyncStreamWriterFunc : IStreamWriter, IAsyncStreamWriter, IHasOptions
|
public class AsyncStreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the source stream.
|
/// Gets or sets the source stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The source stream.</value>
|
/// <value>The source stream.</value>
|
||||||
private Func<Stream, Task> Writer { get; set; }
|
private IAsyncStreamSource _source;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the options.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The options.</value>
|
|
||||||
public IDictionary<string, string> Options { get; private set; }
|
|
||||||
|
|
||||||
public Action OnComplete { get; set; }
|
public Action OnComplete { get; set; }
|
||||||
public Action OnError { get; set; }
|
public Action OnError { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
|
/// Initializes a new instance of the <see cref="AsyncStreamWriter" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AsyncStreamWriterFunc(Func<Stream, Task> writer, IDictionary<string, string> headers)
|
public AsyncStreamWriter(IAsyncStreamSource source)
|
||||||
{
|
{
|
||||||
Writer = writer;
|
_source = source;
|
||||||
|
}
|
||||||
if (headers == null)
|
|
||||||
{
|
public IDictionary<string, string> Options
|
||||||
headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var hasOptions = _source as IHasOptions;
|
||||||
|
if (hasOptions != null)
|
||||||
|
{
|
||||||
|
return hasOptions.Options;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
Options = headers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -44,13 +47,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
/// <param name="responseStream">The response stream.</param>
|
/// <param name="responseStream">The response stream.</param>
|
||||||
public void WriteTo(Stream responseStream)
|
public void WriteTo(Stream responseStream)
|
||||||
{
|
{
|
||||||
var task = Writer(responseStream);
|
var task = _source.WriteToAsync(responseStream);
|
||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WriteToAsync(Stream responseStream)
|
public async Task WriteToAsync(Stream responseStream)
|
||||||
{
|
{
|
||||||
await Writer(responseStream).ConfigureAwait(false);
|
await _source.WriteToAsync(responseStream).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -331,6 +331,46 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string NormalizeConfiguredLocalAddress(string address)
|
||||||
|
{
|
||||||
|
var index = address.Trim('/').IndexOf('/');
|
||||||
|
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
address = address.Substring(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return address.Trim('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ValidateHost(Uri url)
|
||||||
|
{
|
||||||
|
var hosts = _config
|
||||||
|
.Configuration
|
||||||
|
.LocalNetworkAddresses
|
||||||
|
.Select(NormalizeConfiguredLocalAddress)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (hosts.Count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = url.Host ?? string.Empty;
|
||||||
|
|
||||||
|
_logger.Debug("Validating host {0}", host);
|
||||||
|
|
||||||
|
if (_networkManager.IsInPrivateAddressSpace(host))
|
||||||
|
{
|
||||||
|
hosts.Add("localhost");
|
||||||
|
hosts.Add("127.0.0.1");
|
||||||
|
|
||||||
|
return hosts.Any(i => host.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overridable method that can be used to implement a custom hnandler
|
/// Overridable method that can be used to implement a custom hnandler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -350,6 +390,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
return ;
|
return ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ValidateHost(url))
|
||||||
|
{
|
||||||
|
httpRes.StatusCode = 400;
|
||||||
|
httpRes.ContentType = "text/plain";
|
||||||
|
httpRes.Write("Invalid host");
|
||||||
|
|
||||||
|
httpRes.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var operationName = httpReq.OperationName;
|
var operationName = httpReq.OperationName;
|
||||||
var localPath = url.LocalPath;
|
var localPath = url.LocalPath;
|
||||||
|
|
||||||
|
|
|
@ -275,7 +275,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
|
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
|
||||||
{
|
{
|
||||||
responseHeaders["ETag"] = cacheKeyString;
|
responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString);
|
||||||
|
|
||||||
if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
|
if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
|
||||||
{
|
{
|
||||||
|
@ -534,7 +534,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue))
|
if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue))
|
||||||
{
|
{
|
||||||
AddAgeHeader(responseHeaders, lastDateModified);
|
AddAgeHeader(responseHeaders, lastDateModified);
|
||||||
responseHeaders["LastModified"] = lastDateModified.Value.ToString("r");
|
responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cacheDuration.HasValue)
|
if (cacheDuration.HasValue)
|
||||||
|
@ -704,9 +704,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null)
|
public object GetAsyncStreamWriter(IAsyncStreamSource streamSource)
|
||||||
{
|
{
|
||||||
return new AsyncStreamWriterFunc(streamWriter, responseHeaders);
|
return new AsyncStreamWriter(streamSource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -61,6 +61,11 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
|
|
||||||
public void RestartTimer()
|
public void RestartTimer()
|
||||||
{
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
{
|
{
|
||||||
if (_timer == null)
|
if (_timer == null)
|
||||||
|
@ -254,6 +259,11 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
// File may have been deleted
|
// File may have been deleted
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
Logger.Debug("No write permission for: {0}.", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
//the file is unavailable because it is:
|
//the file is unavailable because it is:
|
||||||
|
@ -281,8 +291,10 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_disposed = true;
|
||||||
DisposeTimer();
|
DisposeTimer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user