mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-17 02:49:05 -07:00
commit
c974641a35
@ -33,7 +33,9 @@ namespace Emby.Drawing.GDI
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
graphics.CompositingMode = CompositingMode.SourceCopy;
|
||||
|
||||
// SourceCopy causes the image to be blank in OSX
|
||||
//graphics.CompositingMode = CompositingMode.SourceCopy;
|
||||
|
||||
for (var row = 0; row < rows; row++)
|
||||
{
|
||||
@ -44,21 +46,11 @@ namespace Emby.Drawing.GDI
|
||||
|
||||
if (files.Count > index)
|
||||
{
|
||||
using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
fileStream.CopyTo(memoryStream);
|
||||
|
||||
memoryStream.Position = 0;
|
||||
|
||||
using (var imgtemp = Image.FromStream(memoryStream, true, false))
|
||||
using (var imgtemp = Image.FromFile(files[index]))
|
||||
{
|
||||
graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
@ -90,7 +82,9 @@ namespace Emby.Drawing.GDI
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
graphics.CompositingMode = CompositingMode.SourceCopy;
|
||||
|
||||
// SourceCopy causes the image to be blank in OSX
|
||||
//graphics.CompositingMode = CompositingMode.SourceCopy;
|
||||
|
||||
for (var row = 0; row < rows; row++)
|
||||
{
|
||||
@ -99,21 +93,10 @@ namespace Emby.Drawing.GDI
|
||||
var x = col * singleSize;
|
||||
var y = row * singleSize;
|
||||
|
||||
using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
fileStream.CopyTo(memoryStream);
|
||||
|
||||
memoryStream.Position = 0;
|
||||
|
||||
using (var imgtemp = Image.FromStream(memoryStream, true, false))
|
||||
using (var imgtemp = Image.FromFile(files[index]))
|
||||
{
|
||||
graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
@ -121,16 +104,5 @@ namespace Emby.Drawing.GDI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream GetStream(Image image)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
|
||||
image.Save(ms, ImageFormat.Png);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,9 +119,11 @@ namespace Emby.Drawing.GDI
|
||||
thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
|
||||
thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
thumbnailGraph.CompositingMode = !hasPostProcessing ?
|
||||
CompositingMode.SourceCopy :
|
||||
CompositingMode.SourceOver;
|
||||
|
||||
// SourceCopy causes the image to be blank in OSX
|
||||
//thumbnailGraph.CompositingMode = !hasPostProcessing ?
|
||||
// CompositingMode.SourceCopy :
|
||||
// CompositingMode.SourceOver;
|
||||
|
||||
SetBackgroundColor(thumbnailGraph, options);
|
||||
|
||||
|
@ -63,6 +63,15 @@ namespace MediaBrowser.Api
|
||||
|
||||
Instance = this;
|
||||
_sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
|
||||
_sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
|
||||
}
|
||||
|
||||
private void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
|
||||
{
|
||||
PingTranscodingJob(e.PlaySessionId, e.IsPaused);
|
||||
}
|
||||
}
|
||||
|
||||
void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
|
||||
@ -126,9 +135,10 @@ namespace MediaBrowser.Api
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
var jobCount = _activeTranscodingJobs.Count;
|
||||
var list = _activeTranscodingJobs.ToList();
|
||||
var jobCount = list.Count;
|
||||
|
||||
Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, false, path => true));
|
||||
Parallel.ForEach(list, j => KillTranscodingJob(j, false, path => true));
|
||||
|
||||
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
|
||||
if (jobCount > 0)
|
||||
@ -182,13 +192,13 @@ namespace MediaBrowser.Api
|
||||
|
||||
_activeTranscodingJobs.Add(job);
|
||||
|
||||
ReportTranscodingProgress(job, state, null, null, null, null);
|
||||
ReportTranscodingProgress(job, state, null, null, null, null, null);
|
||||
|
||||
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;
|
||||
|
||||
@ -198,6 +208,7 @@ namespace MediaBrowser.Api
|
||||
job.CompletionPercentage = percentComplete;
|
||||
job.TranscodingPositionTicks = ticks;
|
||||
job.BytesTranscoded = bytesTranscoded;
|
||||
job.BitRate = bitRate;
|
||||
}
|
||||
|
||||
var deviceId = state.Request.DeviceId;
|
||||
@ -209,7 +220,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
|
||||
{
|
||||
Bitrate = state.TotalOutputBitrate,
|
||||
Bitrate = bitRate ?? state.TotalOutputBitrate,
|
||||
AudioCodec = audioCodec,
|
||||
VideoCodec = videoCodec,
|
||||
Container = state.OutputContainer,
|
||||
@ -348,7 +359,7 @@ namespace MediaBrowser.Api
|
||||
return;
|
||||
}
|
||||
|
||||
var timerDuration = 1000;
|
||||
var timerDuration = 10000;
|
||||
|
||||
if (job.Type != TranscodingJobType.Progressive)
|
||||
{
|
||||
@ -400,7 +411,7 @@ namespace MediaBrowser.Api
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Debug("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
|
||||
Logger.Info("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
|
||||
|
||||
KillTranscodingJob(job, true, path => true);
|
||||
}
|
||||
@ -558,13 +569,13 @@ namespace MediaBrowser.Api
|
||||
{
|
||||
|
||||
}
|
||||
catch (IOException ex)
|
||||
catch (IOException)
|
||||
{
|
||||
//Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
|
||||
|
||||
DeletePartialStreamFiles(path, jobType, retryCount + 1, 500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
//Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
|
||||
}
|
||||
@ -684,6 +695,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
public long? BytesDownloaded { get; set; }
|
||||
public long? BytesTranscoded { get; set; }
|
||||
public int? BitRate { get; set; }
|
||||
|
||||
public long? TranscodingPositionTicks { get; set; }
|
||||
public long? DownloadPositionTicks { get; set; }
|
||||
|
@ -139,6 +139,10 @@ namespace MediaBrowser.Api
|
||||
{
|
||||
options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value;
|
||||
}
|
||||
if (hasDtoOptions.EnableUserData.HasValue)
|
||||
{
|
||||
options.EnableUserData = hasDtoOptions.EnableUserData.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ namespace MediaBrowser.Api
|
||||
public interface IHasDtoOptions : IHasItemFields
|
||||
{
|
||||
bool? EnableImages { get; set; }
|
||||
bool? EnableUserData { get; set; }
|
||||
|
||||
int? ImageTypeLimit { get; set; }
|
||||
|
||||
|
@ -573,11 +573,9 @@ namespace MediaBrowser.Api.Images
|
||||
|
||||
var outputFormats = GetOutputFormats(request, imageInfo, cropwhitespace, supportedImageEnhancers);
|
||||
|
||||
var cacheGuid = new Guid(_imageProcessor.GetImageCacheTag(item, imageInfo, supportedImageEnhancers));
|
||||
|
||||
TimeSpan? cacheDuration = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Tag) && cacheGuid == new Guid(request.Tag))
|
||||
if (!string.IsNullOrEmpty(request.Tag))
|
||||
{
|
||||
cacheDuration = TimeSpan.FromDays(365);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using MediaBrowser.Controller.Providers;
|
||||
using ServiceStack;
|
||||
using System.Threading;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
||||
namespace MediaBrowser.Api
|
||||
{
|
||||
@ -39,12 +40,14 @@ namespace MediaBrowser.Api
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ItemRefreshService(ILibraryManager libraryManager, IProviderManager providerManager, IFileSystem fileSystem)
|
||||
public ItemRefreshService(ILibraryManager libraryManager, IProviderManager providerManager, IFileSystem fileSystem, ILogger logger)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_providerManager = providerManager;
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -69,7 +72,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request)
|
||||
{
|
||||
return new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
return new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
MetadataRefreshMode = request.MetadataRefreshMode,
|
||||
ImageRefreshMode = request.ImageRefreshMode,
|
||||
|
@ -304,7 +304,7 @@ namespace MediaBrowser.Api
|
||||
item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : (DateTime?)null;
|
||||
item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : (DateTime?)null;
|
||||
item.ProductionYear = request.ProductionYear;
|
||||
item.OfficialRating = request.OfficialRating;
|
||||
item.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
|
||||
item.CustomRating = request.CustomRating;
|
||||
|
||||
SetProductionLocations(item, request);
|
||||
|
@ -154,9 +154,12 @@ namespace MediaBrowser.Api.Library
|
||||
|
||||
public void Post(PerformOrganization request)
|
||||
{
|
||||
// Don't await this
|
||||
var task = _iFileOrganizationService.PerformOrganization(request.Id);
|
||||
|
||||
Task.WaitAll(task);
|
||||
// Async processing (close dialog early instead of waiting until the file has been copied)
|
||||
// Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display
|
||||
task.Wait(2000);
|
||||
}
|
||||
|
||||
public void Post(OrganizeEpisode request)
|
||||
@ -168,6 +171,7 @@ namespace MediaBrowser.Api.Library
|
||||
dicNewProviderIds = request.NewSeriesProviderIds;
|
||||
}
|
||||
|
||||
// Don't await this
|
||||
var task = _iFileOrganizationService.PerformEpisodeOrganization(new EpisodeFileOrganizationRequest
|
||||
{
|
||||
EndingEpisodeNumber = request.EndingEpisodeNumber,
|
||||
@ -182,11 +186,9 @@ namespace MediaBrowser.Api.Library
|
||||
TargetFolder = request.TargetFolder
|
||||
});
|
||||
|
||||
// For async processing (close dialog early instead of waiting until the file has been copied)
|
||||
//var tasks = new Task[] { task };
|
||||
//Task.WaitAll(tasks, 8000);
|
||||
|
||||
Task.WaitAll(task);
|
||||
// Async processing (close dialog early instead of waiting until the file has been copied)
|
||||
// Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display
|
||||
task.Wait(2000);
|
||||
}
|
||||
|
||||
public object Get(GetSmartMatchInfos request)
|
||||
|
@ -10,6 +10,9 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
namespace MediaBrowser.Api.Library
|
||||
{
|
||||
@ -52,6 +55,8 @@ namespace MediaBrowser.Api.Library
|
||||
/// </summary>
|
||||
/// <value>The path.</value>
|
||||
public string[] Paths { get; set; }
|
||||
|
||||
public LibraryOptions LibraryOptions { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Library/VirtualFolders", "DELETE")]
|
||||
@ -136,6 +141,14 @@ namespace MediaBrowser.Api.Library
|
||||
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>
|
||||
/// Class LibraryStructureService
|
||||
/// </summary>
|
||||
@ -184,13 +197,22 @@ namespace MediaBrowser.Api.Library
|
||||
return ToOptimizedSerializedResultUsingCache(result);
|
||||
}
|
||||
|
||||
public void Post(UpdateLibraryOptions request)
|
||||
{
|
||||
var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(request.Id);
|
||||
|
||||
collectionFolder.UpdateLibraryOptions(request.LibraryOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts the specified request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
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>
|
||||
|
@ -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")]
|
||||
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()
|
||||
{
|
||||
AddCurrentProgram = true;
|
||||
@ -149,6 +152,9 @@ namespace MediaBrowser.Api.LiveTv
|
||||
|
||||
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()
|
||||
{
|
||||
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")]
|
||||
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>
|
||||
/// Fields to return within the items, in addition to basic information
|
||||
/// </summary>
|
||||
@ -331,6 +340,9 @@ namespace MediaBrowser.Api.LiveTv
|
||||
/// <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)]
|
||||
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")]
|
||||
@ -726,7 +738,12 @@ namespace MediaBrowser.Api.LiveTv
|
||||
|
||||
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>
|
||||
{
|
||||
@ -737,6 +754,14 @@ namespace MediaBrowser.Api.LiveTv
|
||||
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)
|
||||
{
|
||||
var user = string.IsNullOrWhiteSpace(request.UserId) ? null : _userManager.GetUserById(request.UserId);
|
||||
@ -997,10 +1022,7 @@ namespace MediaBrowser.Api.LiveTv
|
||||
|
||||
public async Task<object> Get(GetRecordingGroup request)
|
||||
{
|
||||
var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery
|
||||
{
|
||||
|
||||
}, CancellationToken.None).ConfigureAwait(false);
|
||||
var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery(), CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var group = result.Items.FirstOrDefault(i => string.Equals(i.Id, request.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
|
@ -192,7 +192,8 @@ namespace MediaBrowser.Api.Movies
|
||||
SortOrder = SortOrder.Descending,
|
||||
Limit = 7,
|
||||
ParentId = parentIdGuid,
|
||||
Recursive = true
|
||||
Recursive = true,
|
||||
IsPlayed = true
|
||||
};
|
||||
|
||||
var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();
|
||||
|
@ -22,6 +22,8 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
|
||||
namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
@ -69,6 +71,9 @@ namespace MediaBrowser.Api.Playback
|
||||
protected IZipClient ZipClient { get; private set; }
|
||||
protected IJsonSerializer JsonSerializer { get; private set; }
|
||||
|
||||
public static IServerApplicationHost AppHost;
|
||||
public static IHttpClient HttpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
||||
/// </summary>
|
||||
@ -286,28 +291,46 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
protected string GetH264Encoder(StreamState state)
|
||||
{
|
||||
var defaultEncoder = "libx264";
|
||||
|
||||
// Only use alternative encoders for video files.
|
||||
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
||||
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
|
||||
if (state.VideoType == VideoType.VideoFile)
|
||||
{
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
var hwType = encodingOptions.HardwareAccelerationType;
|
||||
|
||||
if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "h264_qsv";
|
||||
return GetAvailableEncoder("h264_qsv", defaultEncoder);
|
||||
}
|
||||
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "h264_nvenc";
|
||||
return GetAvailableEncoder("h264_nvenc", defaultEncoder);
|
||||
}
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "h264_omx";
|
||||
return GetAvailableEncoder("h264_omx", defaultEncoder);
|
||||
}
|
||||
if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice))
|
||||
{
|
||||
return GetAvailableEncoder("h264_vaapi", defaultEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
return "libx264";
|
||||
return defaultEncoder;
|
||||
}
|
||||
|
||||
private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder)
|
||||
{
|
||||
if (MediaEncoder.SupportsEncoder(preferredEncoder))
|
||||
{
|
||||
return preferredEncoder;
|
||||
}
|
||||
return defaultEncoder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -409,7 +432,8 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
|
||||
{
|
||||
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// not supported by h264_omx
|
||||
param += " -profile:v " + state.VideoRequest.Profile;
|
||||
@ -464,7 +488,8 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
!string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-pix_fmt yuv420p " + param;
|
||||
}
|
||||
@ -530,11 +555,48 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
var filters = new List<string>();
|
||||
|
||||
if (state.DeInterlace)
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filters.Add("format=nv12|vaapi");
|
||||
filters.Add("hwupload");
|
||||
}
|
||||
else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filters.Add("yadif=0:-1:0");
|
||||
}
|
||||
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Work around vaapi's reduced scaling features
|
||||
var scaler = "scale_vaapi";
|
||||
|
||||
// Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
|
||||
// (outputWidth, outputHeight). The user may request precise output dimensions or maximum
|
||||
// output dimensions. Output dimensions are guaranteed to be even.
|
||||
decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width);
|
||||
decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height);
|
||||
decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth;
|
||||
decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight;
|
||||
decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth;
|
||||
decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight;
|
||||
|
||||
if (outputWidth > maximumWidth || outputHeight > maximumHeight)
|
||||
{
|
||||
var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
|
||||
outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
|
||||
outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
|
||||
}
|
||||
|
||||
outputWidth = 2 * Math.Truncate(outputWidth / 2);
|
||||
outputHeight = 2 * Math.Truncate(outputHeight / 2);
|
||||
|
||||
if (outputWidth != inputWidth || outputHeight != inputHeight)
|
||||
{
|
||||
filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(UsCulture), outputHeight.ToString(UsCulture)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If fixed dimensions were supplied
|
||||
if (request.Width.HasValue && request.Height.HasValue)
|
||||
{
|
||||
@ -582,7 +644,8 @@ namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
|
||||
|
||||
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
|
||||
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam));
|
||||
}
|
||||
}
|
||||
|
||||
var output = string.Empty;
|
||||
@ -917,6 +980,15 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
}
|
||||
|
||||
if (state.VideoRequest != null)
|
||||
{
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
|
||||
}
|
||||
}
|
||||
|
||||
return arg.Trim();
|
||||
}
|
||||
|
||||
@ -1042,14 +1114,14 @@ namespace MediaBrowser.Api.Playback
|
||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
||||
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))
|
||||
{
|
||||
logFilePrefix = "directstream";
|
||||
logFilePrefix = "ffmpeg-directstream";
|
||||
}
|
||||
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");
|
||||
@ -1080,7 +1152,7 @@ namespace MediaBrowser.Api.Playback
|
||||
//process.BeginOutputReadLine();
|
||||
|
||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||
Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
|
||||
var task = Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
|
||||
|
||||
// Wait for the file to exist before proceeeding
|
||||
while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
|
||||
@ -1099,28 +1171,30 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
|
||||
StartThrottler(state, transcodingJob);
|
||||
ReportUsage(state);
|
||||
|
||||
return transcodingJob;
|
||||
}
|
||||
|
||||
private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
|
||||
{
|
||||
if (EnableThrottling(state) && state.InputProtocol == MediaProtocol.File &&
|
||||
state.RunTimeTicks.HasValue &&
|
||||
state.VideoType == VideoType.VideoFile &&
|
||||
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
|
||||
if (EnableThrottling(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
|
||||
state.TranscodingThrottler.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -1158,6 +1232,7 @@ namespace MediaBrowser.Api.Playback
|
||||
double? percent = null;
|
||||
TimeSpan? transcodingPosition = null;
|
||||
long? bytesTranscoded = null;
|
||||
int? bitRate = null;
|
||||
|
||||
var parts = line.Split(' ');
|
||||
|
||||
@ -1221,11 +1296,32 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rate = part.Split(new[] { '=' }, 2).Last();
|
||||
|
||||
int? scale = null;
|
||||
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
scale = 1024;
|
||||
rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (scale.HasValue)
|
||||
{
|
||||
float val;
|
||||
|
||||
if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
|
||||
{
|
||||
bitRate = (int)Math.Ceiling(val * scale.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (framerate.HasValue || percent.HasValue)
|
||||
{
|
||||
ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded);
|
||||
ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1547,13 +1643,6 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
}
|
||||
else if (i == 25)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
else if (i == 26)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
|
||||
{
|
||||
@ -1564,17 +1653,21 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i == 27)
|
||||
else if (i == 26)
|
||||
{
|
||||
request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
|
||||
}
|
||||
else if (i == 28)
|
||||
else if (i == 27)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
else if (i == 28)
|
||||
{
|
||||
request.Tag = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1788,6 +1881,19 @@ namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
|
||||
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
|
||||
if (!string.IsNullOrWhiteSpace(auth.UserId))
|
||||
{
|
||||
var user = UserManager.GetUserById(auth.UserId);
|
||||
if (!user.Policy.EnableAudioPlaybackTranscoding)
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachMediaSourceInfo(StreamState state,
|
||||
@ -2159,13 +2265,127 @@ namespace MediaBrowser.Api.Playback
|
||||
if (state.VideoRequest != null)
|
||||
{
|
||||
state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
|
||||
state.VideoRequest.ForceLiveStream = transcodingProfile.ForceLiveStream;
|
||||
state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void ReportUsage(StreamState state)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ReportUsageInternal(state).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Task ReportUsageInternal(StreamState state)
|
||||
{
|
||||
if (!ServerConfigurationManager.Configuration.EnableAnonymousUsageReporting)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
if (!MediaEncoder.IsDefaultEncoderPath)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
var dict = new Dictionary<string, string>();
|
||||
|
||||
var outputAudio = GetAudioEncoder(state);
|
||||
if (!string.IsNullOrWhiteSpace(outputAudio))
|
||||
{
|
||||
dict["outputAudio"] = outputAudio;
|
||||
}
|
||||
|
||||
var outputVideo = GetVideoEncoder(state);
|
||||
if (!string.IsNullOrWhiteSpace(outputVideo))
|
||||
{
|
||||
dict["outputVideo"] = outputVideo;
|
||||
}
|
||||
|
||||
if (ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
|
||||
ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
dict["id"] = AppHost.SystemId;
|
||||
dict["type"] = state.VideoRequest == null ? "Audio" : "Video";
|
||||
|
||||
var audioStream = state.AudioStream;
|
||||
if (audioStream != null && !string.IsNullOrWhiteSpace(audioStream.Codec))
|
||||
{
|
||||
dict["inputAudio"] = audioStream.Codec;
|
||||
}
|
||||
|
||||
var videoStream = state.VideoStream;
|
||||
if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec))
|
||||
{
|
||||
dict["inputVideo"] = videoStream.Codec;
|
||||
}
|
||||
|
||||
var cert = GetType().Assembly.GetModules().First().GetSignerCertificate();
|
||||
if (cert != null)
|
||||
{
|
||||
dict["assemblySig"] = cert.GetCertHashString();
|
||||
dict["certSubject"] = cert.Subject ?? string.Empty;
|
||||
dict["certIssuer"] = cert.Issuer ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
if (state.SupportedAudioCodecs.Count > 0)
|
||||
{
|
||||
dict["supportedAudioCodecs"] = string.Join(",", state.SupportedAudioCodecs.ToArray());
|
||||
}
|
||||
|
||||
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
|
||||
|
||||
dict["appName"] = auth.Client ?? string.Empty;
|
||||
dict["appVersion"] = auth.Version ?? string.Empty;
|
||||
dict["device"] = auth.Device ?? string.Empty;
|
||||
dict["deviceId"] = auth.DeviceId ?? string.Empty;
|
||||
dict["context"] = "streaming";
|
||||
|
||||
//Logger.Info(JsonSerializer.SerializeToString(dict));
|
||||
if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
|
||||
list.Add(outputAudio);
|
||||
ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
|
||||
}
|
||||
|
||||
if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
|
||||
list.Add(outputVideo);
|
||||
ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
|
||||
}
|
||||
|
||||
ServerConfigurationManager.SaveConfiguration();
|
||||
|
||||
//Logger.Info(JsonSerializer.SerializeToString(dict));
|
||||
var options = new HttpRequestOptions()
|
||||
{
|
||||
Url = "https://mb3admin.com/admin/service/transcoding/report",
|
||||
CancellationToken = CancellationToken.None,
|
||||
LogRequest = false,
|
||||
LogErrors = false
|
||||
};
|
||||
options.RequestContent = JsonSerializer.SerializeToString(dict);
|
||||
options.RequestContentType = "application/json";
|
||||
|
||||
return HttpClient.Post(options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the dlna headers.
|
||||
/// </summary>
|
||||
|
@ -3,7 +3,6 @@ using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
@ -282,11 +281,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
{
|
||||
var isLiveStream = (state.RunTimeTicks ?? 0) == 0;
|
||||
|
||||
if (state.VideoRequest.ForceLiveStream)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return isLiveStream;
|
||||
}
|
||||
}
|
||||
|
@ -257,8 +257,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// 256k
|
||||
private const int BufferSize = 262144;
|
||||
private const int BufferSize = 81920;
|
||||
|
||||
private long GetStartPositionTicks(StreamState state, int requestedIndex)
|
||||
{
|
||||
|
@ -284,6 +284,13 @@ namespace MediaBrowser.Api.Playback
|
||||
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
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||
@ -315,6 +322,13 @@ namespace MediaBrowser.Api.Playback
|
||||
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
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||
|
@ -142,7 +142,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
var outputPath = state.OutputFilePath;
|
||||
var outputPathExists = FileSystem.FileExists(outputPath);
|
||||
|
||||
var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive);
|
||||
var transcodingJob = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
|
||||
var isTranscodeCached = outputPathExists && transcodingJob != null;
|
||||
|
||||
AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached);
|
||||
|
||||
@ -153,42 +154,64 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
|
||||
using (state)
|
||||
{
|
||||
TimeSpan? cacheDuration = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Tag))
|
||||
{
|
||||
cacheDuration = TimeSpan.FromDays(365);
|
||||
}
|
||||
|
||||
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||
{
|
||||
ResponseHeaders = responseHeaders,
|
||||
ContentType = contentType,
|
||||
IsHeadRequest = isHeadRequest,
|
||||
Path = state.MediaPath
|
||||
Path = state.MediaPath,
|
||||
CacheDuration = cacheDuration
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Not static but transcode cache file exists
|
||||
if (isTranscodeCached)
|
||||
{
|
||||
var contentType = state.GetMimeType(outputPath);
|
||||
//// Not static but transcode cache file exists
|
||||
//if (isTranscodeCached && state.VideoRequest == null)
|
||||
//{
|
||||
// var contentType = state.GetMimeType(outputPath);
|
||||
|
||||
try
|
||||
{
|
||||
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||
{
|
||||
ResponseHeaders = responseHeaders,
|
||||
ContentType = contentType,
|
||||
IsHeadRequest = isHeadRequest,
|
||||
Path = outputPath
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
state.Dispose();
|
||||
}
|
||||
}
|
||||
// try
|
||||
// {
|
||||
// if (transcodingJob != null)
|
||||
// {
|
||||
// ApiEntryPoint.Instance.OnTranscodeBeginRequest(transcodingJob);
|
||||
// }
|
||||
|
||||
// return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||
// {
|
||||
// ResponseHeaders = responseHeaders,
|
||||
// ContentType = contentType,
|
||||
// IsHeadRequest = isHeadRequest,
|
||||
// Path = outputPath,
|
||||
// FileShare = FileShare.ReadWrite,
|
||||
// OnComplete = () =>
|
||||
// {
|
||||
// if (transcodingJob != null)
|
||||
// {
|
||||
// ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
|
||||
// }
|
||||
// }
|
||||
|
||||
// }).ConfigureAwait(false);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// state.Dispose();
|
||||
// }
|
||||
//}
|
||||
|
||||
// Need to start ffmpeg
|
||||
try
|
||||
{
|
||||
return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
|
||||
.ConfigureAwait(false);
|
||||
return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -347,9 +370,9 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
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
|
||||
{
|
||||
@ -368,7 +391,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
|
||||
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;
|
||||
|
@ -1,41 +1,58 @@
|
||||
using MediaBrowser.Model.Logging;
|
||||
using ServiceStack.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using System.Collections.Generic;
|
||||
using ServiceStack.Web;
|
||||
|
||||
namespace MediaBrowser.Api.Playback.Progressive
|
||||
{
|
||||
public class ProgressiveFileCopier
|
||||
public class ProgressiveFileCopier : IAsyncStreamSource, IHasOptions
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly TranscodingJob _job;
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _path;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
private readonly Dictionary<string, string> _outputHeaders;
|
||||
|
||||
// 256k
|
||||
private const int BufferSize = 262144;
|
||||
private const int BufferSize = 81920;
|
||||
|
||||
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;
|
||||
_path = path;
|
||||
_outputHeaders = outputHeaders;
|
||||
_job = job;
|
||||
_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
|
||||
{
|
||||
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)
|
||||
{
|
||||
var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, cancellationToken).ConfigureAwait(false);
|
||||
var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//var position = fs.Position;
|
||||
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
||||
@ -46,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
{
|
||||
eofCount++;
|
||||
}
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
await Task.Delay(100, _cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -55,6 +72,14 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_job != null)
|
||||
{
|
||||
ApiEntryPoint.Instance.OnTranscodeEndRequest(_job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
|
||||
{
|
||||
|
@ -153,7 +153,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||
return args;
|
||||
}
|
||||
|
||||
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
|
||||
var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
|
||||
5.ToString(UsCulture));
|
||||
|
||||
args += keyFrameArg;
|
||||
|
@ -74,6 +74,7 @@ namespace MediaBrowser.Api.Playback
|
||||
public string Params { get; set; }
|
||||
public string PlaySessionId { get; set; }
|
||||
public string LiveStreamId { get; set; }
|
||||
public string Tag { get; set; }
|
||||
}
|
||||
|
||||
public class VideoStreamRequest : StreamRequest
|
||||
@ -192,8 +193,6 @@ namespace MediaBrowser.Api.Playback
|
||||
[ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool CopyTimestamps { get; set; }
|
||||
|
||||
public bool ForceLiveStream { get; set; }
|
||||
|
||||
public bool EnableSubtitlesInManifest { get; set; }
|
||||
|
||||
public VideoStreamRequest()
|
||||
|
@ -80,7 +80,10 @@ namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
@ -208,7 +211,7 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
private async void DisposeLiveStream()
|
||||
{
|
||||
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId))
|
||||
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -72,7 +72,7 @@ namespace MediaBrowser.Api
|
||||
}
|
||||
|
||||
[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")]
|
||||
public string Id { get; set; }
|
||||
@ -104,6 +104,18 @@ namespace MediaBrowser.Api
|
||||
/// <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)]
|
||||
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]
|
||||
|
@ -227,7 +227,7 @@ namespace MediaBrowser.Api
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
//Logger.ErrorException("Error getting plugin list", ex);
|
||||
// Play it safe here
|
||||
|
@ -275,8 +275,6 @@ namespace MediaBrowser.Api.Reports
|
||||
case ItemFilter.IsPlayed:
|
||||
query.IsPlayed = true;
|
||||
break;
|
||||
case ItemFilter.IsRecentlyAdded:
|
||||
break;
|
||||
case ItemFilter.IsResumable:
|
||||
query.IsResumable = true;
|
||||
break;
|
||||
|
@ -29,8 +29,20 @@ namespace MediaBrowser.Api
|
||||
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>
|
||||
/// Gets or sets the user id.
|
||||
/// </summary>
|
||||
|
@ -117,7 +117,7 @@ namespace MediaBrowser.Api
|
||||
config.EnableStandaloneMusicKeys = true;
|
||||
config.EnableCaseSensitiveItemIds = true;
|
||||
//config.EnableFolderView = true;
|
||||
config.SchemaVersion = 108;
|
||||
config.SchemaVersion = 109;
|
||||
}
|
||||
|
||||
public void Post(UpdateStartupConfiguration request)
|
||||
|
@ -24,7 +24,20 @@ namespace MediaBrowser.Api.Sync
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (item.IsFolder && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
|
||||
if (item.IsAudio)
|
||||
{
|
||||
options.Add(SyncJobOption.Quality);
|
||||
options.Add(SyncJobOption.Profile);
|
||||
break;
|
||||
}
|
||||
if (item.IsMusicGenre || item.IsArtist|| item.IsType("musicalbum"))
|
||||
{
|
||||
options.Add(SyncJobOption.Quality);
|
||||
options.Add(SyncJobOption.Profile);
|
||||
options.Add(SyncJobOption.ItemLimit);
|
||||
break;
|
||||
}
|
||||
if (item.IsFolderItem && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
|
||||
{
|
||||
options.Add(SyncJobOption.Quality);
|
||||
options.Add(SyncJobOption.Profile);
|
||||
@ -44,7 +57,7 @@ namespace MediaBrowser.Api.Sync
|
||||
{
|
||||
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.ItemLimit);
|
||||
|
@ -66,6 +66,7 @@ namespace MediaBrowser.Api.Sync
|
||||
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")]
|
||||
public class CancelItems : IReturnVoid
|
||||
{
|
||||
@ -211,7 +212,7 @@ namespace MediaBrowser.Api.Sync
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
public void Delete(CancelItems request)
|
||||
public void Any(CancelItems request)
|
||||
{
|
||||
var itemIds = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
@ -290,7 +291,8 @@ namespace MediaBrowser.Api.Sync
|
||||
{
|
||||
Fields = new List<ItemFields>
|
||||
{
|
||||
ItemFields.SyncInfo
|
||||
ItemFields.SyncInfo,
|
||||
ItemFields.BasicSyncInfo
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -69,6 +69,9 @@ namespace MediaBrowser.Api
|
||||
|
||||
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
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")]
|
||||
@ -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")]
|
||||
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.")]
|
||||
@ -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")]
|
||||
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")]
|
||||
@ -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")]
|
||||
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>
|
||||
@ -409,23 +423,14 @@ namespace MediaBrowser.Api
|
||||
throw new ResourceNotFoundException("No series exists with Id " + request.Id);
|
||||
}
|
||||
|
||||
var seasons = series.GetSeasons(user);
|
||||
|
||||
if (request.IsSpecialSeason.HasValue)
|
||||
var seasons = (await series.GetItems(new InternalItemsQuery(user)
|
||||
{
|
||||
var val = request.IsSpecialSeason.Value;
|
||||
IsMissing = request.IsMissing,
|
||||
IsVirtualUnaired = request.IsVirtualUnaired,
|
||||
IsSpecialSeason = request.IsSpecialSeason,
|
||||
AdjacentTo = request.AdjacentTo
|
||||
|
||||
seasons = seasons.Where(i => i.IsSpecialSeason == val);
|
||||
}
|
||||
|
||||
seasons = FilterVirtualSeasons(request, seasons);
|
||||
|
||||
// This must be the last filter
|
||||
if (!string.IsNullOrEmpty(request.AdjacentTo))
|
||||
{
|
||||
seasons = UserViewBuilder.FilterForAdjacency(seasons, request.AdjacentTo)
|
||||
.Cast<Season>();
|
||||
}
|
||||
}).ConfigureAwait(false)).Items.OfType<Season>();
|
||||
|
||||
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)
|
||||
{
|
||||
var user = _userManager.GetUserById(request.UserId);
|
||||
@ -490,7 +478,7 @@ namespace MediaBrowser.Api
|
||||
}
|
||||
else
|
||||
{
|
||||
episodes = series.GetEpisodes(user, season);
|
||||
episodes = series.GetSeasonEpisodes(user, season);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -8,8 +8,6 @@ using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using ServiceStack;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace MediaBrowser.Api.UserLibrary
|
||||
|
@ -164,8 +164,6 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
case ItemFilter.IsPlayed:
|
||||
query.IsPlayed = true;
|
||||
break;
|
||||
case ItemFilter.IsRecentlyAdded:
|
||||
break;
|
||||
case ItemFilter.IsResumable:
|
||||
query.IsResumable = true;
|
||||
break;
|
||||
@ -180,9 +178,10 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
|
||||
var result = GetItems(request, query);
|
||||
|
||||
var syncProgess = DtoService.GetSyncedItemProgress(dtoOptions);
|
||||
var dtos = result.Items.Select(i =>
|
||||
{
|
||||
var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, user);
|
||||
var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, syncProgess, user);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes))
|
||||
{
|
||||
@ -213,6 +212,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
dto.AlbumCount = counts.AlbumCount;
|
||||
dto.SongCount = counts.SongCount;
|
||||
dto.GameCount = counts.GameCount;
|
||||
dto.ArtistCount = counts.ArtistCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -325,7 +325,8 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
tuples = ibnItems.Select(i => new Tuple<BaseItem, List<BaseItem>>(i, new List<BaseItem>()));
|
||||
}
|
||||
|
||||
var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user));
|
||||
var syncProgess = DtoService.GetSyncedItemProgress(dtoOptions);
|
||||
var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, syncProgess, user));
|
||||
|
||||
result.Items = dtos.Where(i => i != null).ToArray();
|
||||
|
||||
|
@ -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")]
|
||||
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; }
|
||||
|
||||
|
@ -4,11 +4,9 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using ServiceStack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace MediaBrowser.Api.UserLibrary
|
||||
|
@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities;
|
||||
using ServiceStack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace MediaBrowser.Api.UserLibrary
|
||||
|
@ -1,5 +1,4 @@
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Localization;
|
||||
@ -158,33 +157,11 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Ids))
|
||||
{
|
||||
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)
|
||||
if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null)
|
||||
{
|
||||
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;
|
||||
|
||||
if (userRoot == null)
|
||||
@ -294,8 +271,6 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
case ItemFilter.IsPlayed:
|
||||
query.IsPlayed = true;
|
||||
break;
|
||||
case ItemFilter.IsRecentlyAdded:
|
||||
break;
|
||||
case ItemFilter.IsResumable:
|
||||
query.IsResumable = true;
|
||||
break;
|
||||
|
@ -8,8 +8,6 @@ using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using ServiceStack;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace MediaBrowser.Api.UserLibrary
|
||||
|
@ -12,6 +12,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
|
||||
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")]
|
||||
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()
|
||||
{
|
||||
Limit = 20;
|
||||
@ -262,14 +267,16 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
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;
|
||||
_libraryManager = libraryManager;
|
||||
_userDataRepository = userDataRepository;
|
||||
_dtoService = dtoService;
|
||||
_userViewManager = userViewManager;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -426,12 +433,14 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public object Get(GetItem request)
|
||||
public async Task<object> Get(GetItem request)
|
||||
{
|
||||
var user = _userManager.GetUserById(request.UserId);
|
||||
|
||||
var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
|
||||
|
||||
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
|
||||
|
||||
var dtoOptions = GetDtoOptions(request);
|
||||
|
||||
var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
||||
@ -439,6 +448,27 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
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>
|
||||
/// Gets the specified request.
|
||||
/// </summary>
|
||||
|
@ -11,6 +11,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace MediaBrowser.Api
|
||||
{
|
||||
@ -81,11 +82,18 @@ namespace MediaBrowser.Api
|
||||
|
||||
var dtoOptions = GetDtoOptions(request);
|
||||
|
||||
var video = (Video)item;
|
||||
|
||||
var items = video.GetAdditionalParts()
|
||||
var video = item as Video;
|
||||
BaseItemDto[] items;
|
||||
if (video != null)
|
||||
{
|
||||
items = video.GetAdditionalParts()
|
||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video))
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
items = new BaseItemDto[] { };
|
||||
}
|
||||
|
||||
var result = new ItemsResult
|
||||
{
|
||||
|
@ -55,7 +55,7 @@
|
||||
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.3.5\lib\net45\NLog.dll</HintPath>
|
||||
<HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Patterns.Logging">
|
||||
|
@ -429,17 +429,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||
GC.Collect(2, GCCollectionMode.Forced, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the task.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private Task ExecuteTask(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
return Task.Run(async () => await ScheduledTask.Execute(cancellationToken, progress).ConfigureAwait(false), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Progress_s the progress changed.
|
||||
/// </summary>
|
||||
|
@ -1,6 +1,7 @@
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using CommonIO;
|
||||
@ -24,13 +25,22 @@ namespace MediaBrowser.Common.Implementations.Serialization
|
||||
|
||||
// Need to cache these
|
||||
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
|
||||
private readonly ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer> _serializers =
|
||||
new ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer>();
|
||||
private readonly Dictionary<string, System.Xml.Serialization.XmlSerializer> _serializers =
|
||||
new Dictionary<string, System.Xml.Serialization.XmlSerializer>();
|
||||
|
||||
private System.Xml.Serialization.XmlSerializer GetSerializer(Type type)
|
||||
{
|
||||
var key = type.FullName;
|
||||
return _serializers.GetOrAdd(key, k => new System.Xml.Serialization.XmlSerializer(type));
|
||||
lock (_serializers)
|
||||
{
|
||||
System.Xml.Serialization.XmlSerializer serializer;
|
||||
if (!_serializers.TryGetValue(key, out serializer))
|
||||
{
|
||||
serializer = new System.Xml.Serialization.XmlSerializer(type);
|
||||
_serializers[key] = serializer;
|
||||
}
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -33,7 +33,6 @@ namespace MediaBrowser.Common.Implementations.Updates
|
||||
EnableKeepAlive = false,
|
||||
CancellationToken = cancellationToken,
|
||||
UserAgent = "Emby/3.0"
|
||||
|
||||
};
|
||||
|
||||
if (_cacheLength.Ticks > 0)
|
||||
@ -79,6 +78,69 @@ namespace MediaBrowser.Common.Implementations.Updates
|
||||
};
|
||||
}
|
||||
|
||||
private bool MatchesUpdateLevel(RootObject i, PackageVersionClass updateLevel)
|
||||
{
|
||||
if (updateLevel == PackageVersionClass.Beta)
|
||||
{
|
||||
return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
if (updateLevel == PackageVersionClass.Dev)
|
||||
{
|
||||
return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) ||
|
||||
i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// Technically all we need to do is check that it's not pre-release
|
||||
// But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
|
||||
return !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) &&
|
||||
!i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public async Task<List<RootObject>> GetLatestReleases(string organzation, string repository, string assetFilename, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<RootObject>();
|
||||
|
||||
var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
EnableKeepAlive = false,
|
||||
CancellationToken = cancellationToken,
|
||||
UserAgent = "Emby/3.0"
|
||||
};
|
||||
|
||||
if (_cacheLength.Ticks > 0)
|
||||
{
|
||||
options.CacheMode = CacheMode.Unconditional;
|
||||
options.CacheLength = _cacheLength;
|
||||
}
|
||||
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
var obj = _jsonSerializer.DeserializeFromStream<RootObject[]>(stream);
|
||||
|
||||
obj = obj.Where(i => (i.assets ?? new List<Asset>()).Any(a => IsAsset(a, assetFilename))).ToArray();
|
||||
|
||||
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Release)).OrderByDescending(GetVersion).Take(1));
|
||||
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Beta)).OrderByDescending(GetVersion).Take(1));
|
||||
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Dev)).OrderByDescending(GetVersion).Take(1));
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public Version GetVersion(RootObject obj)
|
||||
{
|
||||
Version version;
|
||||
if (!Version.TryParse(obj.tag_name, out version))
|
||||
{
|
||||
return new Version(1, 0);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private CheckForUpdateResult CheckForUpdateResult(RootObject obj, Version minVersion, string assetFilename, string packageName, string targetFilename)
|
||||
{
|
||||
Version version;
|
||||
|
@ -2,7 +2,7 @@
|
||||
<packages>
|
||||
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
|
||||
<package id="morelinq" version="1.4.0" targetFramework="net45" />
|
||||
<package id="NLog" version="4.3.5" targetFramework="net45" />
|
||||
<package id="NLog" version="4.3.6" targetFramework="net45" />
|
||||
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
||||
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
|
||||
</packages>
|
@ -9,11 +9,11 @@ namespace MediaBrowser.Common.IO
|
||||
/// <summary>
|
||||
/// The default copy to buffer size
|
||||
/// </summary>
|
||||
public const int DefaultCopyToBufferSize = 262144;
|
||||
public const int DefaultCopyToBufferSize = 81920;
|
||||
|
||||
/// <summary>
|
||||
/// The default file stream buffer size
|
||||
/// </summary>
|
||||
public const int DefaultFileStreamBufferSize = 262144;
|
||||
public const int DefaultFileStreamBufferSize = 81920;
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ namespace MediaBrowser.Common.Plugins
|
||||
{
|
||||
return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
|
||||
}
|
||||
|
@ -57,10 +57,7 @@ namespace MediaBrowser.Controller.Channels
|
||||
catch
|
||||
{
|
||||
// Already logged at lower levels
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
|
||||
};
|
||||
return new QueryResult<BaseItem>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,8 +83,7 @@ namespace MediaBrowser.Controller.Channels
|
||||
{
|
||||
var list = new List<MediaStream>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(info.VideoCodec) &&
|
||||
!string.IsNullOrWhiteSpace(info.AudioCodec))
|
||||
if (!string.IsNullOrWhiteSpace(info.VideoCodec))
|
||||
{
|
||||
list.Add(new MediaStream
|
||||
{
|
||||
@ -99,7 +98,10 @@ namespace MediaBrowser.Controller.Channels
|
||||
BitRate = info.VideoBitrate,
|
||||
AverageFrameRate = info.Framerate
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(info.AudioCodec))
|
||||
{
|
||||
list.Add(new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Audio,
|
||||
|
@ -19,12 +19,16 @@ namespace MediaBrowser.Controller.Dto
|
||||
public bool EnableImages { get; set; }
|
||||
public bool AddProgramRecordingInfo { get; set; }
|
||||
public string DeviceId { get; set; }
|
||||
public bool EnableUserData { get; set; }
|
||||
public bool AddCurrentProgram { get; set; }
|
||||
|
||||
public DtoOptions()
|
||||
{
|
||||
Fields = new List<ItemFields>();
|
||||
ImageTypeLimit = int.MaxValue;
|
||||
EnableImages = true;
|
||||
EnableUserData = true;
|
||||
AddCurrentProgram = true;
|
||||
|
||||
Fields = Enum.GetNames(typeof (ItemFields))
|
||||
.Select(i => (ItemFields) Enum.Parse(typeof (ItemFields), i, true))
|
||||
|
@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
|
||||
namespace MediaBrowser.Controller.Dto
|
||||
{
|
||||
@ -43,14 +43,6 @@ namespace MediaBrowser.Controller.Dto
|
||||
/// <returns>Task{BaseItemDto}.</returns>
|
||||
BaseItemDto GetBaseItemDto(BaseItem item, List<ItemFields> fields, User user = null, BaseItem owner = null);
|
||||
|
||||
/// <summary>
|
||||
/// Fills the synchronize information.
|
||||
/// </summary>
|
||||
/// <param name="tuples">The tuples.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
void FillSyncInfo(IEnumerable<Tuple<BaseItem, BaseItemDto>> tuples, DtoOptions options, User user);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base item dto.
|
||||
/// </summary>
|
||||
@ -89,11 +81,8 @@ namespace MediaBrowser.Controller.Dto
|
||||
/// <summary>
|
||||
/// Gets the item by name dto.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="taggedItems">The tagged items.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>BaseItemDto.</returns>
|
||||
BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null);
|
||||
BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, Dictionary<string, SyncedItemProgress> syncProgress, User user = null);
|
||||
|
||||
Dictionary<string, SyncedItemProgress> GetSyncedItemProgress(DtoOptions options);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
|
||||
@ -67,6 +69,31 @@ namespace MediaBrowser.Controller.Entities
|
||||
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;
|
||||
public override bool RequiresRefresh()
|
||||
{
|
||||
@ -76,7 +103,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
var locations = PhysicalLocations.ToList();
|
||||
|
||||
var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList();
|
||||
var newLocations = CreateResolveArgs(new DirectoryService(Logger, FileSystem), false).PhysicalLocations.ToList();
|
||||
|
||||
if (!locations.SequenceEqual(newLocations))
|
||||
{
|
||||
@ -89,6 +116,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public override bool BeforeMetadataRefresh()
|
||||
{
|
||||
ClearCache();
|
||||
|
||||
var changed = base.BeforeMetadataRefresh() || _requiresRefresh;
|
||||
_requiresRefresh = false;
|
||||
return changed;
|
||||
@ -96,9 +125,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
|
||||
{
|
||||
ClearCache();
|
||||
|
||||
var path = ContainingFolderPath;
|
||||
|
||||
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths , directoryService)
|
||||
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
|
||||
{
|
||||
FileInfo = FileSystem.GetDirectoryInfo(path),
|
||||
Path = path,
|
||||
@ -136,6 +167,21 @@ namespace MediaBrowser.Controller.Entities
|
||||
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>
|
||||
/// Adds the virtual child.
|
||||
/// </summary>
|
||||
@ -151,15 +197,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
_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>
|
||||
/// Finds the virtual child.
|
||||
/// </summary>
|
||||
|
@ -5,9 +5,11 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities.Audio
|
||||
@ -47,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override bool EnableForceSaveOnDateModifiedChange
|
||||
public override bool EnableRefreshOnDateModifiedChange
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
@ -266,6 +268,11 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
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.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());
|
||||
return list;
|
||||
}
|
||||
|
||||
public override string PresentationUniqueKey
|
||||
{
|
||||
get
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
|
||||
}
|
||||
}
|
||||
protected override bool GetBlockUnratedValue(UserPolicy config)
|
||||
{
|
||||
return config.BlockUnratedItems.Contains(UnratedItem.Music);
|
||||
@ -274,5 +270,54 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
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());
|
||||
return list;
|
||||
}
|
||||
|
||||
public override string PresentationUniqueKey
|
||||
{
|
||||
get
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
return GetUserDataKeys()[0];
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override bool SupportsAddingToPlaylist
|
||||
@ -96,5 +92,48 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
|
||||
return LibraryManager.GetItemList(query);
|
||||
}
|
||||
|
||||
public static string GetPath(string name, bool normalizeName = true)
|
||||
{
|
||||
// Trim the period at the end because windows will have a hard time with that
|
||||
var validName = normalizeName ?
|
||||
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
|
||||
name;
|
||||
|
||||
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.MusicGenrePath, validName);
|
||||
}
|
||||
|
||||
private string GetRebasedPath()
|
||||
{
|
||||
return GetPath(System.IO.Path.GetFileName(Path), false);
|
||||
}
|
||||
|
||||
public override bool RequiresRefresh()
|
||||
{
|
||||
var newPath = GetRebasedPath();
|
||||
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
|
||||
return true;
|
||||
}
|
||||
return base.RequiresRefresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called before any metadata refresh and returns true or false indicating if changes were made
|
||||
/// </summary>
|
||||
public override bool BeforeMetadataRefresh()
|
||||
{
|
||||
var hasChanges = base.BeforeMetadataRefresh();
|
||||
|
||||
var newPath = GetRebasedPath();
|
||||
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
Path = newPath;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -281,6 +281,20 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
public Task UpdateIsOffline(bool newValue)
|
||||
{
|
||||
var item = this;
|
||||
|
||||
if (item.IsOffline != newValue)
|
||||
{
|
||||
item.IsOffline = newValue;
|
||||
// this is creating too many repeated db updates
|
||||
//return item.UpdateToRepository(ItemUpdateType.None, CancellationToken.None);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the location.
|
||||
/// </summary>
|
||||
@ -290,10 +304,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsOffline)
|
||||
{
|
||||
return LocationType.Offline;
|
||||
}
|
||||
//if (IsOffline)
|
||||
//{
|
||||
// return LocationType.Offline;
|
||||
//}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Path))
|
||||
{
|
||||
@ -455,7 +469,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
public DateTime DateLastRefreshed { get; set; }
|
||||
|
||||
[IgnoreDataMember]
|
||||
public virtual bool EnableForceSaveOnDateModifiedChange
|
||||
public virtual bool EnableRefreshOnDateModifiedChange
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
@ -767,6 +781,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
[IgnoreDataMember]
|
||||
public string OfficialRating { get; set; }
|
||||
|
||||
[IgnoreDataMember]
|
||||
public int InheritedParentalRatingValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the critic rating.
|
||||
/// </summary>
|
||||
@ -951,7 +968,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
.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>()
|
||||
.Select(audio =>
|
||||
{
|
||||
@ -981,7 +998,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
.Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => directoryService.GetFiles(i.FullName));
|
||||
|
||||
return LibraryManager.ResolvePaths(files, directoryService, null)
|
||||
return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
|
||||
.OfType<Video>()
|
||||
.Select(item =>
|
||||
{
|
||||
@ -1003,7 +1020,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public Task RefreshMetadata(CancellationToken cancellationToken)
|
||||
{
|
||||
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken);
|
||||
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1194,10 +1211,17 @@ namespace MediaBrowser.Controller.Entities
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public virtual string PresentationUniqueKey
|
||||
public virtual string CreatePresentationUniqueKey()
|
||||
{
|
||||
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()
|
||||
@ -2206,6 +2230,15 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public virtual bool StopRefreshIfLocalMetadataFound
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Guid> GetIdsForAncestorQuery()
|
||||
{
|
||||
return new[] { Id };
|
||||
|
@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override bool EnableForceSaveOnDateModifiedChange
|
||||
public override bool EnableRefreshOnDateModifiedChange
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
@ -3,11 +3,15 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MoreLinq;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
@ -18,6 +22,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
public class CollectionFolder : Folder, ICollectionFolder
|
||||
{
|
||||
public static IXmlSerializer XmlSerializer { get; set; }
|
||||
|
||||
public CollectionFolder()
|
||||
{
|
||||
PhysicalLocationsList = new List<string>();
|
||||
@ -39,6 +45,72 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public string CollectionType { get; set; }
|
||||
|
||||
private static readonly Dictionary<string, LibraryOptions> LibraryOptions = new Dictionary<string, LibraryOptions>();
|
||||
public LibraryOptions GetLibraryOptions()
|
||||
{
|
||||
lock (LibraryOptions)
|
||||
{
|
||||
LibraryOptions options;
|
||||
if (!LibraryOptions.TryGetValue(Path, out options))
|
||||
{
|
||||
options = LoadLibraryOptions();
|
||||
LibraryOptions[Path] = options;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
private LibraryOptions LoadLibraryOptions()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(Path)) as LibraryOptions;
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return new LibraryOptions();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return new LibraryOptions();
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return new LibraryOptions();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorException("Error loading library options", ex);
|
||||
|
||||
return new LibraryOptions();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLibraryOptionsPath(string path)
|
||||
{
|
||||
return System.IO.Path.Combine(path, "options.xml");
|
||||
}
|
||||
|
||||
public void UpdateLibraryOptions(LibraryOptions options)
|
||||
{
|
||||
SaveLibraryOptions(Path, options);
|
||||
}
|
||||
|
||||
public static void SaveLibraryOptions(string path, LibraryOptions options)
|
||||
{
|
||||
lock (LibraryOptions)
|
||||
{
|
||||
LibraryOptions[path] = options;
|
||||
|
||||
options.SchemaVersion = 1;
|
||||
XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow different display preferences for each collection folder
|
||||
/// </summary>
|
||||
@ -82,7 +154,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
var locations = PhysicalLocations.ToList();
|
||||
|
||||
var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList();
|
||||
var newLocations = CreateResolveArgs(new DirectoryService(Logger, FileSystem), false).PhysicalLocations.ToList();
|
||||
|
||||
if (!locations.SequenceEqual(newLocations))
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Dto;
|
||||
@ -14,6 +13,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Model.Channels;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
@ -273,13 +274,14 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
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
|
||||
return GetCachedChildren();
|
||||
}
|
||||
|
||||
public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(FileSystem)));
|
||||
return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -373,7 +375,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child))
|
||||
{
|
||||
await UpdateIsOffline(currentChild, false).ConfigureAwait(false);
|
||||
await currentChild.UpdateIsOffline(false).ConfigureAwait(false);
|
||||
validChildren.Add(currentChild);
|
||||
|
||||
continue;
|
||||
@ -402,7 +404,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
|
||||
{
|
||||
await UpdateIsOffline(item, true).ConfigureAwait(false);
|
||||
await item.UpdateIsOffline(true).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -459,17 +461,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
private Task UpdateIsOffline(BaseItem item, bool newValue)
|
||||
{
|
||||
if (item.IsOffline != newValue)
|
||||
{
|
||||
item.IsOffline = newValue;
|
||||
return item.UpdateToRepository(ItemUpdateType.None, CancellationToken.None);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var children = ActualChildren.ToList();
|
||||
@ -643,8 +634,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
|
||||
{
|
||||
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>
|
||||
@ -699,7 +691,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
items = GetRecursiveChildren(user, query);
|
||||
}
|
||||
|
||||
return PostFilterAndSort(items, query);
|
||||
return PostFilterAndSort(items, query, true, true);
|
||||
}
|
||||
|
||||
if (!(this is UserRootFolder) && !(this is AggregateFolder))
|
||||
@ -883,6 +875,15 @@ namespace MediaBrowser.Controller.Entities
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query.IsPlayed.HasValue)
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(typeof(Series).Name))
|
||||
{
|
||||
Logger.Debug("Query requires post-filtering due to IsPlayed");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -890,8 +891,16 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
if (query.ItemIds.Length > 0)
|
||||
{
|
||||
var specificItems = query.ItemIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
|
||||
return Task.FromResult(PostFilterAndSort(specificItems, query));
|
||||
var result = LibraryManager.GetItemsResult(query);
|
||||
|
||||
if (query.SortBy.Length == 0)
|
||||
{
|
||||
var ids = query.ItemIds.ToList();
|
||||
|
||||
// Try to preserve order
|
||||
result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
|
||||
}
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
return GetItemsInternal(query);
|
||||
@ -919,10 +928,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
catch
|
||||
{
|
||||
// Already logged at lower levels
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
|
||||
};
|
||||
return new QueryResult<BaseItem>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -950,12 +956,12 @@ namespace MediaBrowser.Controller.Entities
|
||||
: GetChildren(user, true).Where(filter);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -1419,7 +1425,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
itemDto.RecursiveItemCount = allItemsQueryResult.TotalRecordCount;
|
||||
}
|
||||
|
||||
double recursiveItemCount = allItemsQueryResult.TotalRecordCount;
|
||||
var recursiveItemCount = allItemsQueryResult.TotalRecordCount;
|
||||
double unplayedCount = unplayedQueryResult.TotalRecordCount;
|
||||
|
||||
if (recursiveItemCount > 0)
|
||||
@ -1429,6 +1435,14 @@ namespace MediaBrowser.Controller.Entities
|
||||
dto.Played = dto.PlayedPercentage.Value >= 100;
|
||||
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]
|
||||
public override bool EnableForceSaveOnDateModifiedChange
|
||||
public override bool EnableRefreshOnDateModifiedChange
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
@ -16,13 +16,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
return list;
|
||||
}
|
||||
|
||||
public override string PresentationUniqueKey
|
||||
{
|
||||
get
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
return GetUserDataKeys()[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the folder containing the item.
|
||||
@ -87,5 +84,48 @@ namespace MediaBrowser.Controller.Entities
|
||||
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());
|
||||
return list;
|
||||
}
|
||||
|
||||
public override string PresentationUniqueKey
|
||||
{
|
||||
get
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
return GetUserDataKeys()[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the folder containing the item.
|
||||
@ -91,5 +87,48 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPath(string name, bool normalizeName = true)
|
||||
{
|
||||
// Trim the period at the end because windows will have a hard time with that
|
||||
var validName = normalizeName ?
|
||||
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
|
||||
name;
|
||||
|
||||
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GenrePath, validName);
|
||||
}
|
||||
|
||||
private string GetRebasedPath()
|
||||
{
|
||||
return GetPath(System.IO.Path.GetFileName(Path), false);
|
||||
}
|
||||
|
||||
public override bool RequiresRefresh()
|
||||
{
|
||||
var newPath = GetRebasedPath();
|
||||
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
|
||||
return true;
|
||||
}
|
||||
return base.RequiresRefresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called before any metadata refresh and returns true or false indicating if changes were made
|
||||
/// </summary>
|
||||
public override bool BeforeMetadataRefresh()
|
||||
{
|
||||
var hasChanges = base.BeforeMetadataRefresh();
|
||||
|
||||
var newPath = GetRebasedPath();
|
||||
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
Path = newPath;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
@ -52,6 +52,15 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
bool RequiresRefresh();
|
||||
|
||||
bool EnableForceSaveOnDateModifiedChange { get; }
|
||||
bool EnableRefreshOnDateModifiedChange { get; }
|
||||
|
||||
string PresentationUniqueKey { get; set; }
|
||||
|
||||
string GetPresentationUniqueKey();
|
||||
string CreatePresentationUniqueKey();
|
||||
bool StopRefreshIfLocalMetadataFound { get; }
|
||||
|
||||
int? GetInheritedParentalRatingValue();
|
||||
int InheritedParentalRatingValue { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
|
@ -37,6 +37,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
public string[] Genres { get; set; }
|
||||
public string[] Keywords { get; set; }
|
||||
|
||||
public bool? IsSpecialSeason { get; set; }
|
||||
public bool? IsMissing { get; set; }
|
||||
public bool? IsUnaired { get; set; }
|
||||
public bool? IsVirtualUnaired { get; set; }
|
||||
@ -50,6 +51,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public string PresentationUniqueKey { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string PathNotStartsWith { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string SlugName { get; set; }
|
||||
|
||||
|
@ -11,11 +11,13 @@ namespace MediaBrowser.Controller.Entities
|
||||
public int? MaxListOrder { get; set; }
|
||||
public Guid AppearsInItemId { get; set; }
|
||||
public string NameContains { get; set; }
|
||||
public SourceType[] SourceTypes { get; set; }
|
||||
|
||||
public InternalPeopleQuery()
|
||||
{
|
||||
PersonTypes = new List<string>();
|
||||
ExcludePersonTypes = new List<string>();
|
||||
SourceTypes = new SourceType[] { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,26 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
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]
|
||||
public override bool IsPreSorted
|
||||
{
|
||||
@ -75,9 +95,23 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
protected override bool SupportsShortcutChildren
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsLegacyBoxSet)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
private bool IsLegacyBoxSet
|
||||
{
|
||||
get
|
||||
{
|
||||
return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsAuthorizedToDelete(User user)
|
||||
|
@ -179,5 +179,15 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override bool StopRefreshIfLocalMetadataFound
|
||||
{
|
||||
get
|
||||
{
|
||||
// Need people id's from internet metadata
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
|
@ -26,14 +26,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
|
||||
return list;
|
||||
}
|
||||
|
||||
public override string PresentationUniqueKey
|
||||
{
|
||||
get
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
return GetUserDataKeys()[0];
|
||||
}
|
||||
}
|
||||
|
||||
public PersonLookupInfo GetLookupInfo()
|
||||
{
|
||||
@ -126,6 +122,64 @@ namespace MediaBrowser.Controller.Entities
|
||||
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>
|
||||
|
@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override bool EnableForceSaveOnDateModifiedChange
|
||||
public override bool EnableRefreshOnDateModifiedChange
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
@ -18,14 +18,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
|
||||
return list;
|
||||
}
|
||||
|
||||
public override string PresentationUniqueKey
|
||||
{
|
||||
get
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
return GetUserDataKeys()[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the folder containing the item.
|
||||
@ -89,5 +85,48 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPath(string name, bool normalizeName = true)
|
||||
{
|
||||
// Trim the period at the end because windows will have a hard time with that
|
||||
var validName = normalizeName ?
|
||||
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
|
||||
name;
|
||||
|
||||
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.StudioPath, validName);
|
||||
}
|
||||
|
||||
private string GetRebasedPath()
|
||||
{
|
||||
return GetPath(System.IO.Path.GetFileName(Path), false);
|
||||
}
|
||||
|
||||
public override bool RequiresRefresh()
|
||||
{
|
||||
var newPath = GetRebasedPath();
|
||||
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
|
||||
return true;
|
||||
}
|
||||
return base.RequiresRefresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called before any metadata refresh and returns true or false indicating if changes were made
|
||||
/// </summary>
|
||||
public override bool BeforeMetadataRefresh()
|
||||
{
|
||||
var hasChanges = base.BeforeMetadataRefresh();
|
||||
|
||||
var newPath = GetRebasedPath();
|
||||
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
Path = newPath;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,11 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
[IgnoreDataMember]
|
||||
public Series Series
|
||||
{
|
||||
get { return FindParent<Series>(); }
|
||||
get
|
||||
{
|
||||
var seriesId = SeriesId ?? FindSeriesId();
|
||||
return seriesId.HasValue ? (LibraryManager.GetItemById(seriesId.Value) as Series) : null;
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
@ -143,24 +147,8 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
{
|
||||
get
|
||||
{
|
||||
var season = FindParent<Season>();
|
||||
|
||||
// Episodes directly in series folder
|
||||
if (season == null)
|
||||
{
|
||||
var series = Series;
|
||||
|
||||
if (series != null && ParentIndexNumber.HasValue)
|
||||
{
|
||||
var findNumber = ParentIndexNumber.Value;
|
||||
|
||||
season = series.Children
|
||||
.OfType<Season>()
|
||||
.FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
|
||||
}
|
||||
}
|
||||
|
||||
return season;
|
||||
var seasonId = SeasonId ?? FindSeasonId();
|
||||
return seasonId.HasValue ? (LibraryManager.GetItemById(seasonId.Value) as Season) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +181,23 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
public Guid? FindSeasonId()
|
||||
{
|
||||
var season = Season;
|
||||
var season = FindParent<Season>();
|
||||
|
||||
// Episodes directly in series folder
|
||||
if (season == null)
|
||||
{
|
||||
var series = Series;
|
||||
|
||||
if (series != null && ParentIndexNumber.HasValue)
|
||||
{
|
||||
var findNumber = ParentIndexNumber.Value;
|
||||
|
||||
season = series.Children
|
||||
.OfType<Season>()
|
||||
.FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
|
||||
}
|
||||
}
|
||||
|
||||
return season == null ? (Guid?)null : season.Id;
|
||||
}
|
||||
|
||||
@ -263,7 +267,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
public Guid? FindSeriesId()
|
||||
{
|
||||
var series = Series;
|
||||
var series = FindParent<Series>();
|
||||
return series == null ? (Guid?)null : series.Id;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Users;
|
||||
using MoreLinq;
|
||||
@ -86,7 +85,11 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
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>
|
||||
@ -96,7 +99,11 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
[IgnoreDataMember]
|
||||
public Series Series
|
||||
{
|
||||
get { return FindParent<Series>(); }
|
||||
get
|
||||
{
|
||||
var seriesId = SeriesId ?? FindSeriesId();
|
||||
return seriesId.HasValue ? (LibraryManager.GetItemById(seriesId.Value) as Series) : null;
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
@ -115,10 +122,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override string PresentationUniqueKey
|
||||
{
|
||||
get
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
if (IndexNumber.HasValue)
|
||||
{
|
||||
@ -129,8 +133,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
}
|
||||
}
|
||||
|
||||
return base.PresentationUniqueKey;
|
||||
}
|
||||
return base.CreatePresentationUniqueKey();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -142,24 +145,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
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)
|
||||
{
|
||||
if (query.User == null)
|
||||
@ -171,10 +156,15 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
|
||||
|
||||
var id = Guid.NewGuid().ToString("N");
|
||||
|
||||
Logger.Debug("Season.GetItemsInternal entering GetEpisodes. Request id: " + id);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -185,19 +175,17 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
/// <returns>IEnumerable{Episode}.</returns>
|
||||
public IEnumerable<Episode> GetEpisodes(User user)
|
||||
{
|
||||
var config = user.Configuration;
|
||||
|
||||
return GetEpisodes(Series, user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
|
||||
return GetEpisodes(Series, user);
|
||||
}
|
||||
|
||||
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()
|
||||
@ -257,7 +245,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
public Guid? FindSeriesId()
|
||||
{
|
||||
var series = Series;
|
||||
var series = FindParent<Series>();
|
||||
return series == null ? (Guid?)null : series.Id;
|
||||
}
|
||||
|
||||
|
@ -96,19 +96,29 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override string PresentationUniqueKey
|
||||
{
|
||||
get
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
var userdatakeys = GetUserDataKeys();
|
||||
|
||||
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)
|
||||
@ -117,7 +127,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
{
|
||||
return series.Id.ToString("N");
|
||||
}
|
||||
return series.PresentationUniqueKey;
|
||||
return series.GetPresentationUniqueKey();
|
||||
}
|
||||
|
||||
public override int GetChildCount(User user)
|
||||
@ -197,7 +207,30 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
{
|
||||
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)
|
||||
@ -227,55 +260,43 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
|
||||
|
||||
var items = GetSeasons(user).Where(filter);
|
||||
var result = PostFilterAndSort(items, query);
|
||||
var result = PostFilterAndSort(items, query, false, true);
|
||||
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)
|
||||
{
|
||||
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(new InternalItemsQuery(user)
|
||||
{
|
||||
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name },
|
||||
SortBy = new[] { ItemSortBy.SortName }
|
||||
var allItems = LibraryManager.GetItemList(query).ToList();
|
||||
|
||||
}).ToList();
|
||||
Logger.Debug("GetEpisodes return {0} items from database", allItems.Count);
|
||||
|
||||
var allSeriesEpisodes = allItems.OfType<Episode>().ToList();
|
||||
|
||||
var allEpisodes = allItems.OfType<Season>()
|
||||
.SelectMany(i => i.GetEpisodes(this, user, includeMissing, includeVirtualUnaired, allSeriesEpisodes))
|
||||
.SelectMany(i => i.GetEpisodes(this, user, allSeriesEpisodes))
|
||||
.Reverse()
|
||||
.ToList();
|
||||
|
||||
@ -352,80 +373,70 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
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)
|
||||
{
|
||||
return LibraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
Logger.Debug("Series.GetAllEpisodes entering GetItemList");
|
||||
|
||||
var result = LibraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending)
|
||||
.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>
|
||||
/// Filters the episodes by season.
|
||||
/// </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)
|
||||
{
|
||||
return config.BlockUnratedItems.Contains(UnratedItem.Series);
|
||||
@ -509,5 +546,15 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override bool StopRefreshIfLocalMetadataFound
|
||||
{
|
||||
get
|
||||
{
|
||||
// Need people id's from internet metadata
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
@ -126,5 +124,15 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override bool StopRefreshIfLocalMetadataFound
|
||||
{
|
||||
get
|
||||
{
|
||||
// Need people id's from internet metadata
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
Name = newName;
|
||||
|
||||
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem))
|
||||
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem))
|
||||
{
|
||||
ReplaceAllMetadata = true,
|
||||
ImageRefreshMode = ImageRefreshMode.FullRefresh,
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System.Runtime.Serialization;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Library;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using System;
|
||||
@ -8,7 +7,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
@ -18,6 +16,31 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
if (query.Recursive)
|
||||
@ -35,7 +58,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
var user = query.User;
|
||||
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)
|
||||
@ -71,6 +94,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public override bool BeforeMetadataRefresh()
|
||||
{
|
||||
ClearCache();
|
||||
|
||||
var hasChanges = base.BeforeMetadataRefresh();
|
||||
|
||||
if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase))
|
||||
@ -82,11 +107,22 @@ namespace MediaBrowser.Controller.Entities
|
||||
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)
|
||||
{
|
||||
ClearCache();
|
||||
|
||||
await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ClearCache();
|
||||
|
||||
// Not the best way to handle this, but it solves an issue
|
||||
// CollectionFolders aren't always getting saved after changes
|
||||
// This means that grabbing the item by Id may end up returning the old one
|
||||
|
@ -1,5 +1,4 @@
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
@ -14,12 +13,10 @@ using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MoreLinq;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
@ -427,7 +424,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
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)
|
||||
@ -783,7 +780,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
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)
|
||||
@ -794,9 +791,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
private QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
|
||||
BaseItem queryParent,
|
||||
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,
|
||||
@ -804,7 +803,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
int? totalRecordLimit,
|
||||
InternalItemsQuery query,
|
||||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager configurationManager)
|
||||
IServerConfigurationManager configurationManager,
|
||||
bool collapseBoxSetItems,
|
||||
bool enableSorting)
|
||||
{
|
||||
var user = query.User;
|
||||
|
||||
@ -813,7 +814,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.IsVirtualUnaired,
|
||||
query.IsUnaired);
|
||||
|
||||
if (collapseBoxSetItems)
|
||||
{
|
||||
items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager);
|
||||
}
|
||||
|
||||
// This must be the last filter
|
||||
if (!string.IsNullOrEmpty(query.AdjacentTo))
|
||||
@ -821,7 +825,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
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,
|
||||
@ -1096,8 +1100,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
bool? isVirtualUnaired,
|
||||
bool? isUnaired)
|
||||
{
|
||||
items = FilterVirtualSeasons(items, isMissing, isVirtualUnaired, isUnaired);
|
||||
|
||||
if (isMissing.HasValue)
|
||||
{
|
||||
var val = isMissing.Value;
|
||||
@ -1143,65 +1145,14 @@ namespace MediaBrowser.Controller.Entities
|
||||
return items;
|
||||
}
|
||||
|
||||
private static IEnumerable<BaseItem> FilterVirtualSeasons(
|
||||
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,
|
||||
public static QueryResult<BaseItem> SortAndPage(IEnumerable<BaseItem> items,
|
||||
int? totalRecordLimit,
|
||||
InternalItemsQuery query,
|
||||
ILibraryManager libraryManager)
|
||||
ILibraryManager libraryManager, bool enableSorting)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -12,6 +12,7 @@ using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
@ -44,24 +45,23 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override string PresentationUniqueKey
|
||||
{
|
||||
get
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
|
||||
{
|
||||
return PrimaryVersionId;
|
||||
}
|
||||
|
||||
return base.PresentationUniqueKey;
|
||||
}
|
||||
return base.CreatePresentationUniqueKey();
|
||||
}
|
||||
|
||||
[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; }
|
||||
@ -612,6 +612,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
SupportsDirectStream = i.VideoType == VideoType.VideoFile
|
||||
};
|
||||
|
||||
if (info.Protocol == MediaProtocol.File)
|
||||
{
|
||||
info.ETag = i.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N");
|
||||
}
|
||||
|
||||
if (i.IsShortcut)
|
||||
{
|
||||
info.Path = i.ShortcutPath;
|
||||
|
@ -112,5 +112,48 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPath(string name, bool normalizeName = true)
|
||||
{
|
||||
// Trim the period at the end because windows will have a hard time with that
|
||||
var validName = normalizeName ?
|
||||
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
|
||||
name;
|
||||
|
||||
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.YearPath, validName);
|
||||
}
|
||||
|
||||
private string GetRebasedPath()
|
||||
{
|
||||
return GetPath(System.IO.Path.GetFileName(Path), false);
|
||||
}
|
||||
|
||||
public override bool RequiresRefresh()
|
||||
{
|
||||
var newPath = GetRebasedPath();
|
||||
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
|
||||
return true;
|
||||
}
|
||||
return base.RequiresRefresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called before any metadata refresh and returns true or false indicating if changes were made
|
||||
/// </summary>
|
||||
public override bool BeforeMetadataRefresh()
|
||||
{
|
||||
var hasChanges = base.BeforeMetadataRefresh();
|
||||
|
||||
var newPath = GetRebasedPath();
|
||||
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
Path = newPath;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
using MediaBrowser.Model.FileOrganization;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.FileOrganization;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.FileOrganization
|
||||
{
|
||||
public interface IFileOrganizationService
|
||||
{
|
||||
event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemAdded;
|
||||
event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemUpdated;
|
||||
event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemRemoved;
|
||||
event EventHandler LogReset;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the new files.
|
||||
/// </summary>
|
||||
@ -81,5 +88,20 @@ namespace MediaBrowser.Controller.FileOrganization
|
||||
/// <param name="ItemName">Item name.</param>
|
||||
/// <param name="matchString">The match string to delete.</param>
|
||||
void DeleteSmartMatchEntry(string ItemName, string matchString);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to add a an item to the list of currently processed items.
|
||||
/// </summary>
|
||||
/// <param name="result">The result item.</param>
|
||||
/// <param name="fullClientRefresh">Passing true will notify the client to reload all items, otherwise only a single item will be refreshed.</param>
|
||||
/// <returns>True if the item was added, False if the item is already contained in the list.</returns>
|
||||
bool AddToInProgressList(FileOrganizationResult result, bool fullClientRefresh);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the list of currently processed items.
|
||||
/// </summary>
|
||||
/// <param name="result">The result item.</param>
|
||||
/// <returns>True if the item was removed, False if the item was not contained in the list.</returns>
|
||||
bool RemoveFromInprogressList(FileOrganizationResult result);
|
||||
}
|
||||
}
|
||||
|
@ -106,5 +106,7 @@ namespace MediaBrowser.Controller
|
||||
/// </summary>
|
||||
/// <value>The internal metadata path.</value>
|
||||
string InternalMetadataPath { get; }
|
||||
|
||||
string ArtistsPath { get; }
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace MediaBrowser.Controller.Library
|
||||
@ -32,15 +34,11 @@ namespace MediaBrowser.Controller.Library
|
||||
/// <summary>
|
||||
/// Resolves a set of files into a list of BaseItem
|
||||
/// </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,
|
||||
IDirectoryService directoryService,
|
||||
Folder parent, string
|
||||
collectionType = null);
|
||||
Folder parent,
|
||||
LibraryOptions libraryOptions,
|
||||
string collectionType = null);
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
bool IsAudioFile(string path);
|
||||
|
||||
bool IsAudioFile(string path, LibraryOptions libraryOptions);
|
||||
bool IsVideoFile(string path, LibraryOptions libraryOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the season number from path.
|
||||
/// </summary>
|
||||
@ -453,6 +454,8 @@ namespace MediaBrowser.Controller.Library
|
||||
/// <returns>IEnumerable<Folder>.</returns>
|
||||
IEnumerable<Folder> GetCollectionFolders(BaseItem item);
|
||||
|
||||
LibraryOptions GetLibraryOptions(BaseItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the people.
|
||||
/// </summary>
|
||||
@ -474,12 +477,6 @@ namespace MediaBrowser.Controller.Library
|
||||
/// <returns>List<Person>.</returns>
|
||||
List<Person> GetPeopleItems(InternalPeopleQuery query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all people names.
|
||||
/// </summary>
|
||||
/// <returns>List<System.String>.</returns>
|
||||
List<PersonInfo> GetAllPeople();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the people.
|
||||
/// </summary>
|
||||
@ -557,7 +554,7 @@ namespace MediaBrowser.Controller.Library
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
|
||||
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 AddMediaPath(string virtualFolderName, string path);
|
||||
void RemoveMediaPath(string virtualFolderName, string path);
|
||||
@ -568,5 +565,6 @@ namespace MediaBrowser.Controller.Library
|
||||
QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
|
||||
QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
|
||||
QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
|
||||
QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query);
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
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>
|
||||
/// Gets or sets the file system dictionary.
|
||||
/// </summary>
|
||||
|
@ -331,12 +331,11 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task AddInfoToProgramDto(List<Tuple<BaseItem,BaseItemDto>> programs, List<ItemFields> fields, User user = null);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the tuner host.
|
||||
/// </summary>
|
||||
/// <param name="info">The information.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info);
|
||||
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
|
||||
/// <summary>
|
||||
/// Saves the listing provider.
|
||||
/// </summary>
|
||||
|
@ -1,5 +1,4 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
@ -1,5 +1,4 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class TimerEventInfo
|
||||
{
|
||||
|
@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class TunerChannelMapping
|
||||
{
|
||||
|
@ -236,6 +236,7 @@
|
||||
<Compile Include="Net\IAuthorizationContext.cs" />
|
||||
<Compile Include="Net\IAuthService.cs" />
|
||||
<Compile Include="Net\IHasAuthorization.cs" />
|
||||
<Compile Include="Net\IAsyncStreamSource.cs" />
|
||||
<Compile Include="Net\IHasResultFactory.cs" />
|
||||
<Compile Include="Net\IHasSession.cs" />
|
||||
<Compile Include="Net\IHttpResultFactory.cs" />
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
|
@ -1,7 +1,6 @@
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
@ -134,5 +133,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
Task Init();
|
||||
|
||||
Task UpdateEncoderPath(string path, string pathType);
|
||||
bool SupportsEncoder(string encoder);
|
||||
bool IsDefaultEncoderPath { get; }
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return new[] {videoPath};
|
||||
}
|
||||
|
||||
public static List<string> GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, IEnumerable<string> filenames)
|
||||
private static List<string> GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, List<string> filenames)
|
||||
{
|
||||
if (filenames.Count == 0)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var allFiles = fileSystem
|
||||
.GetFilePaths(rootPath, true)
|
||||
.ToList();
|
||||
|
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>
|
||||
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>
|
||||
/// Gets the optimized result.
|
||||
|
@ -169,6 +169,13 @@ namespace MediaBrowser.Controller.Persistence
|
||||
QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
|
||||
QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
|
||||
QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
|
||||
QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query);
|
||||
|
||||
List<string> GetGameGenreNames();
|
||||
List<string> GetMusicGenreNames();
|
||||
List<string> GetStudioNames();
|
||||
List<string> GetGenreNames();
|
||||
List<string> GetAllArtistNames();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
|
||||
namespace MediaBrowser.Controller.Playlists
|
||||
{
|
||||
@ -58,11 +59,22 @@ namespace MediaBrowser.Controller.Playlists
|
||||
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)
|
||||
{
|
||||
return GetPlayableItems(user).Result;
|
||||
}
|
||||
|
||||
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
|
||||
{
|
||||
return new List<BaseItem>();
|
||||
}
|
||||
|
||||
public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
|
||||
{
|
||||
var items = GetPlayableItems(user).Result;
|
||||
|
@ -724,6 +724,15 @@ namespace MediaBrowser.Controller.Providers
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "TvMazeId":
|
||||
{
|
||||
var id = reader.ReadElementContentAsString();
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
item.SetProviderId(MetadataProviders.TvMaze, id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "AudioDbArtistId":
|
||||
{
|
||||
var id = reader.ReadElementContentAsString();
|
||||
|
@ -16,6 +16,9 @@ namespace MediaBrowser.Controller.Providers
|
||||
private readonly ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>> _cache =
|
||||
new ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache =
|
||||
new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public DirectoryService(ILogger logger, IFileSystem fileSystem)
|
||||
{
|
||||
_logger = logger;
|
||||
@ -100,20 +103,19 @@ namespace MediaBrowser.Controller.Providers
|
||||
|
||||
public FileSystemMetadata GetFile(string path)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(path);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
FileSystemMetadata file;
|
||||
if (!_fileCache.TryGetValue(path, out file))
|
||||
{
|
||||
_logger.Debug("Parent path is null for {0}", path);
|
||||
return null;
|
||||
file = _fileSystem.GetFileInfo(path);
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
_fileCache.TryAdd(path, file);
|
||||
}
|
||||
}
|
||||
|
||||
var dict = GetFileSystemDictionary(directory, false);
|
||||
|
||||
FileSystemMetadata entry;
|
||||
dict.TryGetValue(path, out entry);
|
||||
|
||||
return entry;
|
||||
return file;
|
||||
//return _fileSystem.GetFileInfo(path);
|
||||
}
|
||||
|
||||
public IEnumerable<FileSystemMetadata> GetDirectories(string path)
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Controller.Providers
|
||||
@ -19,7 +20,7 @@ namespace MediaBrowser.Controller.Providers
|
||||
public bool ForceSave { get; set; }
|
||||
|
||||
public MetadataRefreshOptions(IFileSystem fileSystem)
|
||||
: this(new DirectoryService(fileSystem))
|
||||
: this(new DirectoryService(new NullLogger(), fileSystem))
|
||||
{
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user