mirror of https://github.com/jellyfin/jellyfin.git
Merge branch 'beta'
This commit is contained in:
commit
f57ce51d12
|
@ -163,7 +163,7 @@ namespace Emby.Drawing
|
|||
return _imageEncoder.SupportedOutputFormats;
|
||||
}
|
||||
|
||||
public async Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options)
|
||||
public async Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
|
@ -178,14 +178,13 @@ namespace Emby.Drawing
|
|||
}
|
||||
|
||||
var originalImagePath = originalImage.Path;
|
||||
var dateModified = originalImage.DateModified;
|
||||
|
||||
if (!_imageEncoder.SupportsImageEncoding)
|
||||
{
|
||||
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
|
||||
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
|
||||
var dateModified = originalImage.DateModified;
|
||||
|
||||
if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
|
||||
{
|
||||
var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
||||
|
@ -211,7 +210,7 @@ namespace Emby.Drawing
|
|||
if (options.HasDefaultOptions(originalImagePath))
|
||||
{
|
||||
// Just spit out the original file if all the options are default
|
||||
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
|
||||
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
|
||||
ImageSize? originalImageSize;
|
||||
|
@ -221,7 +220,7 @@ namespace Emby.Drawing
|
|||
if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
|
||||
{
|
||||
// Just spit out the original file if all the options are default
|
||||
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
|
||||
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
@ -235,10 +234,6 @@ namespace Emby.Drawing
|
|||
var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
|
||||
var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer);
|
||||
|
||||
var semaphore = GetLock(cacheFilePath);
|
||||
|
||||
await semaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
var imageProcessingLockTaken = false;
|
||||
|
||||
try
|
||||
|
@ -251,15 +246,20 @@ namespace Emby.Drawing
|
|||
var newHeight = Convert.ToInt32(newSize.Height);
|
||||
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
||||
var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
|
||||
|
||||
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
imageProcessingLockTaken = true;
|
||||
|
||||
_imageEncoder.EncodeImage(originalImagePath, cacheFilePath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
|
||||
_imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
|
||||
CopyFile(tmpPath, cacheFilePath);
|
||||
|
||||
return new Tuple<string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath));
|
||||
}
|
||||
|
||||
return new Tuple<string, string>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath));
|
||||
return new Tuple<string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -267,7 +267,7 @@ namespace Emby.Drawing
|
|||
_logger.ErrorException("Error encoding image", ex);
|
||||
|
||||
// Just spit out the original file if all the options are default
|
||||
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
|
||||
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -275,8 +275,18 @@ namespace Emby.Drawing
|
|||
{
|
||||
_imageProcessingSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyFile(string src, string destination)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Copy(src, destination, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,14 +422,9 @@ namespace Emby.Drawing
|
|||
|
||||
var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
|
||||
|
||||
var semaphore = GetLock(croppedImagePath);
|
||||
|
||||
await semaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
// Check again in case of contention
|
||||
if (_fileSystem.FileExists(croppedImagePath))
|
||||
{
|
||||
semaphore.Release();
|
||||
return GetResult(croppedImagePath);
|
||||
}
|
||||
|
||||
|
@ -428,11 +433,15 @@ namespace Emby.Drawing
|
|||
try
|
||||
{
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
|
||||
var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
|
||||
|
||||
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
imageProcessingLockTaken = true;
|
||||
|
||||
_imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath);
|
||||
_imageEncoder.CropWhiteSpace(originalImagePath, tmpPath);
|
||||
CopyFile(tmpPath, croppedImagePath);
|
||||
return GetResult(tmpPath);
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
|
@ -452,11 +461,7 @@ namespace Emby.Drawing
|
|||
{
|
||||
_imageProcessingSemaphore.Release();
|
||||
}
|
||||
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
return GetResult(croppedImagePath);
|
||||
}
|
||||
|
||||
private Tuple<string, DateTime> GetResult(string path)
|
||||
|
|
|
@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images
|
|||
{
|
||||
var list = new List<ImageInfo>();
|
||||
|
||||
foreach (var image in item.ImageInfos.Where(i => !item.AllowsMultipleImages(i.Type)))
|
||||
var itemImages = item.ImageInfos;
|
||||
|
||||
foreach (var image in itemImages.Where(i => !item.AllowsMultipleImages(i.Type)))
|
||||
{
|
||||
var info = GetImageInfo(item, image, null);
|
||||
|
||||
|
@ -283,14 +285,14 @@ namespace MediaBrowser.Api.Images
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var imageType in item.ImageInfos.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
|
||||
foreach (var imageType in itemImages.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
// Prevent implicitly captured closure
|
||||
var currentImageType = imageType;
|
||||
|
||||
foreach (var image in item.ImageInfos.Where(i => i.Type == currentImageType))
|
||||
foreach (var image in itemImages.Where(i => i.Type == currentImageType))
|
||||
{
|
||||
var info = GetImageInfo(item, image, index);
|
||||
|
||||
|
@ -636,6 +638,7 @@ namespace MediaBrowser.Api.Images
|
|||
CacheDuration = cacheDuration,
|
||||
ResponseHeaders = headers,
|
||||
ContentType = imageResult.Item2,
|
||||
DateLastModified = imageResult.Item3,
|
||||
IsHeadRequest = isHeadRequest,
|
||||
Path = imageResult.Item1,
|
||||
|
||||
|
|
|
@ -286,19 +286,25 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
protected string GetH264Encoder(StreamState state)
|
||||
{
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
// 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)
|
||||
{
|
||||
return "h264_qsv";
|
||||
}
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "h264_qsv";
|
||||
}
|
||||
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "h264_nvenc";
|
||||
}
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "h264_omx";
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "h264_nvenc";
|
||||
}
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "h264_omx";
|
||||
}
|
||||
}
|
||||
|
||||
return "libx264";
|
||||
|
@ -843,6 +849,14 @@ namespace MediaBrowser.Api.Playback
|
|||
return null;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
|
||||
{
|
||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||
|
|
|
@ -13,6 +13,7 @@ using ServiceStack.Web;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
|
@ -336,17 +337,19 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
state.Dispose();
|
||||
}
|
||||
|
||||
var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
|
||||
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
result.Options["Content-Type"] = contentType;
|
||||
outputHeaders["Content-Type"] = contentType;
|
||||
|
||||
// Add the response headers to the result object
|
||||
foreach (var item in responseHeaders)
|
||||
{
|
||||
result.Options[item.Key] = item.Value;
|
||||
outputHeaders[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
return result;
|
||||
Func<Stream,Task> streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream);
|
||||
|
||||
return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -48,21 +48,19 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
/// <param name="responseStream">The response stream.</param>
|
||||
public void WriteTo(Stream responseStream)
|
||||
{
|
||||
WriteToInternal(responseStream);
|
||||
var task = WriteToAsync(responseStream);
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to async.
|
||||
/// Writes to.
|
||||
/// </summary>
|
||||
/// <param name="responseStream">The response stream.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private void WriteToInternal(Stream responseStream)
|
||||
public async Task WriteToAsync(Stream responseStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream);
|
||||
|
||||
Task.WaitAll(task);
|
||||
await new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream).ConfigureAwait(false);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
@ -110,11 +108,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
var eofCount = 0;
|
||||
long position = 0;
|
||||
|
||||
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, false))
|
||||
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||
{
|
||||
while (eofCount < 15)
|
||||
{
|
||||
CopyToInternal(fs, outputStream, BufferSize);
|
||||
await CopyToInternal(fs, outputStream, BufferSize).ConfigureAwait(false);
|
||||
|
||||
var fsPosition = fs.Position;
|
||||
|
||||
|
@ -140,11 +138,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
}
|
||||
}
|
||||
|
||||
private void CopyToInternal(Stream source, Stream destination, int bufferSize)
|
||||
private async Task CopyToInternal(Stream source, Stream destination, int bufferSize)
|
||||
{
|
||||
var array = new byte[bufferSize];
|
||||
int count;
|
||||
while ((count = source.Read(array, 0, array.Length)) != 0)
|
||||
while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
//if (_job != null)
|
||||
//{
|
||||
|
@ -170,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
// }
|
||||
//}
|
||||
|
||||
destination.Write(array, 0, count);
|
||||
await destination.WriteAsync(array, 0, count).ConfigureAwait(false);
|
||||
|
||||
_bytesWritten += count;
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Reports
|
|||
|
||||
/// <summary> Manager for library. </summary>
|
||||
private readonly ILibraryManager _libraryManager; ///< Manager for library
|
||||
/// <summary> The localization. </summary>
|
||||
/// <summary> The localization. </summary>
|
||||
|
||||
private readonly ILocalizationManager _localization; ///< The localization
|
||||
|
||||
|
@ -58,10 +58,10 @@ namespace MediaBrowser.Api.Reports
|
|||
/// <summary> Gets the given request. </summary>
|
||||
/// <param name="request"> The request. </param>
|
||||
/// <returns> A Task<object> </returns>
|
||||
public async Task<object> Get(GetActivityLogs request)
|
||||
public object Get(GetActivityLogs request)
|
||||
{
|
||||
request.DisplayType = "Screen";
|
||||
ReportResult result = await GetReportActivities(request).ConfigureAwait(false);
|
||||
ReportResult result = GetReportActivities(request);
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,8 @@ namespace MediaBrowser.Api.Reports
|
|||
return null;
|
||||
|
||||
request.DisplayType = "Screen";
|
||||
var reportResult = await GetReportResult(request);
|
||||
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||
var reportResult = await GetReportResult(request, user);
|
||||
|
||||
return ToOptimizedResult(reportResult);
|
||||
}
|
||||
|
@ -117,7 +118,8 @@ namespace MediaBrowser.Api.Reports
|
|||
if (string.IsNullOrEmpty(request.IncludeItemTypes))
|
||||
return null;
|
||||
request.DisplayType = "Screen";
|
||||
var reportResult = await GetReportStatistic(request);
|
||||
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||
var reportResult = await GetReportStatistic(request, user);
|
||||
|
||||
return ToOptimizedResult(reportResult);
|
||||
}
|
||||
|
@ -150,6 +152,7 @@ namespace MediaBrowser.Api.Reports
|
|||
headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
|
||||
headers["Content-Encoding"] = "UTF-8";
|
||||
|
||||
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||
ReportResult result = null;
|
||||
switch (reportViewType)
|
||||
{
|
||||
|
@ -157,12 +160,12 @@ namespace MediaBrowser.Api.Reports
|
|||
case ReportViewType.ReportData:
|
||||
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
|
||||
ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
|
||||
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
|
||||
QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
|
||||
result = dataBuilder.GetResult(queryResult.Items, request);
|
||||
result.TotalRecordCount = queryResult.TotalRecordCount;
|
||||
break;
|
||||
case ReportViewType.ReportActivities:
|
||||
result = await GetReportActivities(request).ConfigureAwait(false);
|
||||
result = GetReportActivities(request);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -177,23 +180,15 @@ namespace MediaBrowser.Api.Reports
|
|||
break;
|
||||
}
|
||||
|
||||
object ro = ResultFactory.GetResult(returnResult, contentType, headers);
|
||||
return ro;
|
||||
return ResultFactory.GetResult(returnResult, contentType, headers);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Private Methods]
|
||||
|
||||
/// <summary> Gets items query. </summary>
|
||||
/// <param name="request"> The request. </param>
|
||||
/// <param name="user"> The user. </param>
|
||||
/// <returns> The items query. </returns>
|
||||
private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
|
||||
{
|
||||
var query = new InternalItemsQuery
|
||||
var query = new InternalItemsQuery(user)
|
||||
{
|
||||
User = user,
|
||||
IsPlayed = request.IsPlayed,
|
||||
MediaTypes = request.GetMediaTypes(),
|
||||
IncludeItemTypes = request.GetIncludeItemTypes(),
|
||||
|
@ -231,6 +226,7 @@ namespace MediaBrowser.Api.Reports
|
|||
Tags = request.GetTags(),
|
||||
OfficialRatings = request.GetOfficialRatings(),
|
||||
Genres = request.GetGenres(),
|
||||
GenreIds = request.GetGenreIds(),
|
||||
Studios = request.GetStudios(),
|
||||
StudioIds = request.GetStudioIds(),
|
||||
Person = request.Person,
|
||||
|
@ -245,9 +241,11 @@ namespace MediaBrowser.Api.Reports
|
|||
MaxPlayers = request.MaxPlayers,
|
||||
MinCommunityRating = request.MinCommunityRating,
|
||||
MinCriticRating = request.MinCriticRating,
|
||||
ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
|
||||
ParentIndexNumber = request.ParentIndexNumber,
|
||||
AiredDuringSeason = request.AiredDuringSeason,
|
||||
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater
|
||||
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
|
||||
EnableTotalRecordCount = request.EnableTotalRecordCount
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Ids))
|
||||
|
@ -357,98 +355,111 @@ namespace MediaBrowser.Api.Reports
|
|||
query.AlbumNames = request.Albums.Split('|');
|
||||
}
|
||||
|
||||
if (request.HasQueryLimit == false)
|
||||
{
|
||||
query.StartIndex = null;
|
||||
query.Limit = null;
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary> Gets query result. </summary>
|
||||
/// <param name="request"> The request. </param>
|
||||
/// <returns> The query result. </returns>
|
||||
private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
|
||||
private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request, User user)
|
||||
{
|
||||
// Placeholder in case needed later
|
||||
// all report queries currently need this because it's not being specified
|
||||
request.Recursive = true;
|
||||
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||
request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
|
||||
|
||||
var parentItem = string.IsNullOrEmpty(request.ParentId) ?
|
||||
(user == null ? _libraryManager.RootFolder : user.RootFolder) :
|
||||
_libraryManager.GetItemById(request.ParentId);
|
||||
|
||||
var item = string.IsNullOrEmpty(request.ParentId) ?
|
||||
user == null ? _libraryManager.RootFolder : user.RootFolder :
|
||||
parentItem;
|
||||
_libraryManager.GetItemById(request.ParentId);
|
||||
|
||||
IEnumerable<BaseItem> items;
|
||||
if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//item = user == null ? _libraryManager.RootFolder : user.RootFolder;
|
||||
}
|
||||
else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item = user == null ? _libraryManager.RootFolder : user.RootFolder;
|
||||
}
|
||||
|
||||
// Default list type = children
|
||||
|
||||
var folder = item as Folder;
|
||||
if (folder == null)
|
||||
{
|
||||
folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Ids))
|
||||
{
|
||||
request.Recursive = true;
|
||||
var query = GetItemsQuery(request, user);
|
||||
var result = await folder.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)
|
||||
{
|
||||
var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||
return result;
|
||||
return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
var userRoot = item as UserRootFolder;
|
||||
|
||||
if (userRoot == null)
|
||||
{
|
||||
var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
items = ((Folder)item).GetChildren(user, true);
|
||||
return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new QueryResult<BaseItem> { Items = items.ToArray() };
|
||||
var userRoot = item as UserRootFolder;
|
||||
|
||||
if (userRoot == null)
|
||||
{
|
||||
return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
IEnumerable<BaseItem> items = folder.GetChildren(user, true);
|
||||
|
||||
var itemsArray = items.ToArray();
|
||||
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
Items = itemsArray,
|
||||
TotalRecordCount = itemsArray.Length
|
||||
};
|
||||
}
|
||||
|
||||
#region [Private Methods]
|
||||
|
||||
/// <summary> Gets report activities. </summary>
|
||||
/// <param name="request"> The request. </param>
|
||||
/// <returns> The report activities. </returns>
|
||||
private Task<ReportResult> GetReportActivities(IReportsDownload request)
|
||||
private ReportResult GetReportActivities(IReportsDownload request)
|
||||
{
|
||||
return Task<ReportResult>.Run(() =>
|
||||
{
|
||||
DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
|
||||
(DateTime?)null :
|
||||
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
|
||||
DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
|
||||
(DateTime?)null :
|
||||
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
|
||||
|
||||
QueryResult<ActivityLogEntry> queryResult;
|
||||
if (request.HasQueryLimit)
|
||||
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
|
||||
else
|
||||
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
|
||||
//var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
|
||||
|
||||
ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
|
||||
var result = builder.GetResult(queryResult, request);
|
||||
result.TotalRecordCount = queryResult.TotalRecordCount;
|
||||
return result;
|
||||
|
||||
});
|
||||
QueryResult<ActivityLogEntry> queryResult;
|
||||
if (request.HasQueryLimit)
|
||||
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
|
||||
else
|
||||
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
|
||||
//var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
|
||||
|
||||
ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
|
||||
var result = builder.GetResult(queryResult, request);
|
||||
result.TotalRecordCount = queryResult.TotalRecordCount;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary> Gets report result. </summary>
|
||||
/// <param name="request"> The request. </param>
|
||||
/// <returns> The report result. </returns>
|
||||
private async Task<ReportResult> GetReportResult(GetItemReport request)
|
||||
private async Task<ReportResult> GetReportResult(GetItemReport request, User user)
|
||||
{
|
||||
ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
|
||||
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
|
||||
QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
|
||||
ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
|
||||
reportResult.TotalRecordCount = queryResult.TotalRecordCount;
|
||||
|
||||
|
@ -458,10 +469,10 @@ namespace MediaBrowser.Api.Reports
|
|||
/// <summary> Gets report statistic. </summary>
|
||||
/// <param name="request"> The request. </param>
|
||||
/// <returns> The report statistic. </returns>
|
||||
private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
|
||||
private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request, User user)
|
||||
{
|
||||
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
|
||||
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
|
||||
QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
|
||||
|
||||
ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
|
||||
ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
|
||||
|
|
|
@ -114,11 +114,10 @@ namespace MediaBrowser.Api
|
|||
private void SetWizardFinishValues(ServerConfiguration config)
|
||||
{
|
||||
config.EnableLocalizedGuids = true;
|
||||
config.EnableCustomPathSubFolders = true;
|
||||
config.EnableStandaloneMusicKeys = true;
|
||||
config.EnableCaseSensitiveItemIds = true;
|
||||
//config.EnableFolderView = true;
|
||||
config.SchemaVersion = 97;
|
||||
config.SchemaVersion = 108;
|
||||
}
|
||||
|
||||
public void Post(UpdateStartupConfiguration request)
|
||||
|
|
|
@ -103,6 +103,8 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
[ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool? IsInBoxSet { get; set; }
|
||||
|
||||
public string ExcludeItemIds { get; set; }
|
||||
|
||||
public bool EnableTotalRecordCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -367,6 +369,11 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public string[] GetExcludeItemIds()
|
||||
{
|
||||
return (ExcludeItemIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public string[] GetExcludeItemTypes()
|
||||
{
|
||||
return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
|
|
@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
{
|
||||
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||
|
||||
var result = await GetItemsToSerialize(request, user).ConfigureAwait(false);
|
||||
var result = await GetQueryResult(request, user).ConfigureAwait(false);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
|
@ -135,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
/// <param name="request">The request.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>IEnumerable{BaseItem}.</returns>
|
||||
private async Task<QueryResult<BaseItem>> GetItemsToSerialize(GetItems request, User user)
|
||||
private async Task<QueryResult<BaseItem>> GetQueryResult(GetItems request, User user)
|
||||
{
|
||||
var item = string.IsNullOrEmpty(request.ParentId) ?
|
||||
user == null ? _libraryManager.RootFolder : user.RootFolder :
|
||||
|
@ -263,7 +263,8 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
ParentIndexNumber = request.ParentIndexNumber,
|
||||
AiredDuringSeason = request.AiredDuringSeason,
|
||||
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
|
||||
EnableTotalRecordCount = request.EnableTotalRecordCount
|
||||
EnableTotalRecordCount = request.EnableTotalRecordCount,
|
||||
ExcludeItemIds = request.GetExcludeItemIds()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Ids))
|
||||
|
|
|
@ -140,7 +140,17 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||
|
||||
private WebRequest GetRequest(HttpRequestOptions options, string method)
|
||||
{
|
||||
var request = CreateWebRequest(options.Url);
|
||||
var url = options.Url;
|
||||
|
||||
var uriAddress = new Uri(url);
|
||||
var userInfo = uriAddress.UserInfo;
|
||||
if (!string.IsNullOrWhiteSpace(userInfo))
|
||||
{
|
||||
_logger.Info("Found userInfo in url: {0} ... url: {1}", userInfo, url);
|
||||
url = url.Replace(userInfo + "@", string.Empty);
|
||||
}
|
||||
|
||||
var request = CreateWebRequest(url);
|
||||
var httpWebRequest = request as HttpWebRequest;
|
||||
|
||||
if (httpWebRequest != null)
|
||||
|
@ -183,9 +193,27 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(userInfo))
|
||||
{
|
||||
var parts = userInfo.Split(':');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
request.Credentials = GetCredential(url, parts[0], parts[1]);
|
||||
request.PreAuthenticate = true;
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private CredentialCache GetCredential(string url, string username, string password)
|
||||
{
|
||||
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
|
||||
CredentialCache credentialCache = new CredentialCache();
|
||||
credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
|
||||
return credentialCache;
|
||||
}
|
||||
|
||||
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
|
||||
{
|
||||
foreach (var header in options.RequestHeaders.ToList())
|
||||
|
|
|
@ -54,7 +54,9 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||
{
|
||||
if (updateLevel == PackageVersionClass.Release)
|
||||
{
|
||||
obj = obj.Where(i => !i.prerelease).ToArray();
|
||||
// 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.
|
||||
obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
}
|
||||
else if (updateLevel == PackageVersionClass.Beta)
|
||||
{
|
||||
|
@ -70,7 +72,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||
.Where(i => i != null)
|
||||
.OrderByDescending(i => Version.Parse(i.AvailableVersion))
|
||||
.FirstOrDefault();
|
||||
|
||||
|
||||
return availableUpdate ?? new CheckForUpdateResult
|
||||
{
|
||||
IsUpdateAvailable = false
|
||||
|
|
|
@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Drawing
|
|||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options);
|
||||
Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enhanced image.
|
||||
|
|
|
@ -1878,7 +1878,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
return new ItemImageInfo
|
||||
{
|
||||
Path = path,
|
||||
DateModified = FileSystem.GetLastWriteTimeUtc(path),
|
||||
DateModified = chapter.ImageDateModified,
|
||||
Type = imageType
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using MediaBrowser.Controller.Providers;
|
||||
using System;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
@ -17,7 +18,26 @@ namespace MediaBrowser.Controller.Entities
|
|||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string SeriesName { get; set; }
|
||||
[IgnoreDataMember]
|
||||
public Guid? SeriesId { get; set; }
|
||||
[IgnoreDataMember]
|
||||
public string SeriesSortName { get; set; }
|
||||
|
||||
public string FindSeriesSortName()
|
||||
{
|
||||
return SeriesSortName;
|
||||
}
|
||||
public string FindSeriesName()
|
||||
{
|
||||
return SeriesName;
|
||||
}
|
||||
|
||||
public Guid? FindSeriesId()
|
||||
{
|
||||
return SeriesId;
|
||||
}
|
||||
|
||||
public override bool CanDownload()
|
||||
{
|
||||
|
|
|
@ -760,11 +760,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue");
|
||||
return true;
|
||||
}
|
||||
if (query.SortBy.Contains(ItemSortBy.SeriesSortName, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName");
|
||||
return true;
|
||||
}
|
||||
if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
|
||||
|
@ -859,24 +854,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
return true;
|
||||
}
|
||||
|
||||
if (query.IsMissing.HasValue)
|
||||
{
|
||||
Logger.Debug("Query requires post-filtering due to IsMissing");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query.IsUnaired.HasValue)
|
||||
{
|
||||
Logger.Debug("Query requires post-filtering due to IsUnaired");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query.IsVirtualUnaired.HasValue)
|
||||
{
|
||||
Logger.Debug("Query requires post-filtering due to IsVirtualUnaired");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
|
||||
{
|
||||
Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems");
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
public interface IHasSeries
|
||||
|
@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// Gets the name of the series.
|
||||
/// </summary>
|
||||
/// <value>The name of the series.</value>
|
||||
string SeriesName { get; }
|
||||
string SeriesName { get; set; }
|
||||
string FindSeriesName();
|
||||
string SeriesSortName { get; set; }
|
||||
string FindSeriesSortName();
|
||||
Guid? SeriesId { get; set; }
|
||||
Guid? FindSeriesId();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// </summary>
|
||||
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
|
||||
{
|
||||
|
||||
public Episode()
|
||||
{
|
||||
RemoteTrailers = new List<MediaUrl>();
|
||||
|
@ -25,11 +24,11 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
public List<Guid> RemoteTrailerIds { get; set; }
|
||||
public List<MediaUrl> RemoteTrailers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the season in which it aired.
|
||||
/// </summary>
|
||||
/// <value>The aired season.</value>
|
||||
public int? AirsBeforeSeasonNumber { get; set; }
|
||||
/// <summary>
|
||||
/// Gets the season in which it aired.
|
||||
/// </summary>
|
||||
/// <value>The aired season.</value>
|
||||
public int? AirsBeforeSeasonNumber { get; set; }
|
||||
public int? AirsAfterSeasonNumber { get; set; }
|
||||
public int? AirsBeforeEpisodeNumber { get; set; }
|
||||
|
||||
|
@ -54,7 +53,16 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// This is the ending episode number for double episodes.
|
||||
/// </summary>
|
||||
/// <value>The index number.</value>
|
||||
public int? IndexNumberEnd { get; set; }
|
||||
public int? IndexNumberEnd { get; set; }
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string SeriesSortName { get; set; }
|
||||
|
||||
public string FindSeriesSortName()
|
||||
{
|
||||
var series = Series;
|
||||
return series == null ? SeriesSortName : series.SortName;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
protected override bool SupportsOwnedItems
|
||||
|
@ -166,13 +174,27 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string SeriesName
|
||||
{
|
||||
get
|
||||
{
|
||||
var series = Series;
|
||||
return series == null ? null : series.Name;
|
||||
}
|
||||
public string SeriesName { get; set; }
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string SeasonName { get; set; }
|
||||
|
||||
public string FindSeasonName()
|
||||
{
|
||||
var season = Season;
|
||||
return season == null ? SeasonName : season.Name;
|
||||
}
|
||||
|
||||
public string FindSeriesName()
|
||||
{
|
||||
var series = Series;
|
||||
return series == null ? SeriesName : series.Name;
|
||||
}
|
||||
|
||||
public Guid? FindSeasonId()
|
||||
{
|
||||
var season = Season;
|
||||
return season == null ? (Guid?)null : season.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -235,20 +257,14 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public Guid? SeasonId
|
||||
{
|
||||
get
|
||||
{
|
||||
// First see if the parent is a Season
|
||||
var season = Season;
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
return season.Id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public Guid? SeasonId { get; set; }
|
||||
[IgnoreDataMember]
|
||||
public Guid? SeriesId { get; set; }
|
||||
|
||||
public Guid? FindSeriesId()
|
||||
{
|
||||
var series = Series;
|
||||
return series == null ? (Guid?)null : series.Id;
|
||||
}
|
||||
|
||||
public override IEnumerable<Guid> GetAncestorIds()
|
||||
|
|
|
@ -51,6 +51,15 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string SeriesSortName { get; set; }
|
||||
|
||||
public string FindSeriesSortName()
|
||||
{
|
||||
var series = Series;
|
||||
return series == null ? SeriesSortName : series.SortName;
|
||||
}
|
||||
|
||||
// Genre, Rating and Stuido will all be the same
|
||||
protected override IEnumerable<string> GetIndexByOptions()
|
||||
{
|
||||
|
@ -235,13 +244,21 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string SeriesName
|
||||
public string SeriesName { get; set; }
|
||||
|
||||
[IgnoreDataMember]
|
||||
public Guid? SeriesId { get; set; }
|
||||
|
||||
public string FindSeriesName()
|
||||
{
|
||||
get
|
||||
{
|
||||
var series = Series;
|
||||
return series == null ? null : series.Name;
|
||||
}
|
||||
var series = Series;
|
||||
return series == null ? SeriesName : series.Name;
|
||||
}
|
||||
|
||||
public Guid? FindSeriesId()
|
||||
{
|
||||
var series = Series;
|
||||
return series == null ? (Guid?)null : series.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -28,6 +28,8 @@ 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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the optimized result.
|
||||
/// </summary>
|
||||
|
|
|
@ -366,6 +366,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
return null;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
|
||||
{
|
||||
if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||
|
|
|
@ -120,6 +120,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
bool preserveOriginalTimestamps,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(itemId))
|
||||
{
|
||||
throw new ArgumentNullException("itemId");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(mediaSourceId))
|
||||
{
|
||||
throw new ArgumentNullException("mediaSourceId");
|
||||
}
|
||||
|
||||
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
|
@ -141,10 +150,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
int subtitleStreamIndex,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(itemId))
|
||||
{
|
||||
throw new ArgumentNullException("itemId");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(mediaSourceId))
|
||||
{
|
||||
throw new ArgumentNullException("mediaSourceId");
|
||||
}
|
||||
|
||||
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var mediaSource = mediaSources
|
||||
.First(i => string.Equals(i.Id, mediaSourceId));
|
||||
.First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var subtitleStream = mediaSource.MediaStreams
|
||||
.First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex);
|
||||
|
@ -609,7 +627,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
throw;
|
||||
}
|
||||
|
||||
process.StandardError.BaseStream.CopyToAsync(logFileStream);
|
||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||
Task.Run(() => StartStreamingLog(process.StandardError.BaseStream, logFileStream));
|
||||
|
||||
var ranToCompletion = process.WaitForExit(300000);
|
||||
|
||||
|
@ -686,6 +705,33 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
}
|
||||
}
|
||||
|
||||
private async Task StartStreamingLog(Stream source, Stream target)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var reader = new StreamReader(source))
|
||||
{
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
|
||||
|
||||
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||
await target.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error reading ffmpeg log", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ass font.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers>
|
||||
<PropertyChanged />
|
||||
</Weavers>
|
|
@ -36,6 +36,8 @@
|
|||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
@ -617,9 +619,6 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
|
||||
<Link>Extensions\FloatHelper.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
|
||||
<Link>Extensions\IHasPropertyChangedEvent.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
|
||||
<Link>Extensions\IntHelper.cs</Link>
|
||||
</Compile>
|
||||
|
@ -1233,13 +1232,6 @@
|
|||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Fody" version="1.29.2" targetFramework="portable45-net45+win8+wp8+wpa81" developmentDependency="true" />
|
||||
<package id="PropertyChanged.Fody" version="1.50.4" targetFramework="portable45-net45+win8+wp8+wpa81" developmentDependency="true" />
|
||||
</packages>
|
|
@ -591,9 +591,6 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
|
||||
<Link>Extensions\FloatHelper.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
|
||||
<Link>Extensions\IHasPropertyChangedEvent.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
|
||||
<Link>Extensions\IntHelper.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -49,12 +49,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
/// </summary>
|
||||
/// <value>The cache path.</value>
|
||||
public string CachePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [enable custom path sub folders].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [enable custom path sub folders]; otherwise, <c>false</c>.</value>
|
||||
public bool EnableCustomPathSubFolders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
|
||||
|
|
|
@ -214,7 +214,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
Migrations = new string[] { };
|
||||
SqliteCacheSize = 0;
|
||||
|
||||
EnableCustomPathSubFolders = true;
|
||||
EnableLocalizedGuids = true;
|
||||
DisplaySpecialsWithinSeasons = true;
|
||||
|
||||
|
|
|
@ -11,8 +11,14 @@ namespace MediaBrowser.Model.Dlna
|
|||
public AudioOptions()
|
||||
{
|
||||
Context = EncodingContext.Streaming;
|
||||
|
||||
EnableDirectPlay = true;
|
||||
EnableDirectStream = true;
|
||||
}
|
||||
|
||||
public bool EnableDirectPlay { get; set; }
|
||||
public bool EnableDirectStream { get; set; }
|
||||
|
||||
public string ItemId { get; set; }
|
||||
public List<MediaSourceInfo> MediaSources { get; set; }
|
||||
public DeviceProfile Profile { get; set; }
|
||||
|
|
|
@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
public ProfileCondition[] Conditions { get; set; }
|
||||
|
||||
public ProfileCondition[] ApplyConditions { get; set; }
|
||||
|
||||
[XmlAttribute("codec")]
|
||||
public string Codec { get; set; }
|
||||
|
||||
|
@ -20,6 +22,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
public CodecProfile()
|
||||
{
|
||||
Conditions = new ProfileCondition[] {};
|
||||
ApplyConditions = new ProfileCondition[] { };
|
||||
}
|
||||
|
||||
public List<string> GetCodecs()
|
||||
|
|
|
@ -131,6 +131,11 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
List<PlayMethod> directPlayMethods = GetAudioDirectPlayMethods(item, audioStream, options);
|
||||
|
||||
ConditionProcessor conditionProcessor = new ConditionProcessor();
|
||||
|
||||
int? inputAudioChannels = audioStream == null ? null : audioStream.Channels;
|
||||
int? inputAudioBitrate = audioStream == null ? null : audioStream.BitDepth;
|
||||
|
||||
if (directPlayMethods.Count > 0)
|
||||
{
|
||||
string audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||
|
@ -138,27 +143,36 @@ namespace MediaBrowser.Model.Dlna
|
|||
// Make sure audio codec profiles are satisfied
|
||||
if (!string.IsNullOrEmpty(audioCodec))
|
||||
{
|
||||
ConditionProcessor conditionProcessor = new ConditionProcessor();
|
||||
|
||||
List<ProfileCondition> conditions = new List<ProfileCondition>();
|
||||
foreach (CodecProfile i in options.Profile.CodecProfiles)
|
||||
{
|
||||
if (i.Type == CodecType.Audio && i.ContainsCodec(audioCodec, item.Container))
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
bool applyConditions = true;
|
||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||
{
|
||||
conditions.Add(c);
|
||||
if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
|
||||
{
|
||||
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
||||
applyConditions = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applyConditions)
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
{
|
||||
conditions.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int? audioChannels = audioStream.Channels;
|
||||
int? audioBitrate = audioStream.BitRate;
|
||||
|
||||
bool all = true;
|
||||
foreach (ProfileCondition c in conditions)
|
||||
{
|
||||
if (!conditionProcessor.IsAudioConditionSatisfied(c, audioChannels, audioBitrate))
|
||||
if (!conditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate))
|
||||
{
|
||||
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
|
||||
all = false;
|
||||
|
@ -241,9 +255,23 @@ namespace MediaBrowser.Model.Dlna
|
|||
List<ProfileCondition> audioTranscodingConditions = new List<ProfileCondition>();
|
||||
foreach (CodecProfile i in audioCodecProfiles)
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
bool applyConditions = true;
|
||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||
{
|
||||
audioTranscodingConditions.Add(c);
|
||||
if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
|
||||
{
|
||||
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
||||
applyConditions = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applyConditions)
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
{
|
||||
audioTranscodingConditions.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,7 +322,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
if (directPlayProfile != null)
|
||||
{
|
||||
// While options takes the network and other factors into account. Only applies to direct stream
|
||||
if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()))
|
||||
if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()) && options.EnableDirectStream)
|
||||
{
|
||||
playMethods.Add(PlayMethod.DirectStream);
|
||||
}
|
||||
|
@ -302,7 +330,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
// The profile describes what the device supports
|
||||
// If device requirements are satisfied then allow both direct stream and direct play
|
||||
if (item.SupportsDirectPlay &&
|
||||
IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
|
||||
IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)) && options.EnableDirectPlay)
|
||||
{
|
||||
playMethods.Add(PlayMethod.DirectPlay);
|
||||
}
|
||||
|
@ -385,8 +413,8 @@ namespace MediaBrowser.Model.Dlna
|
|||
MediaStream videoStream = item.VideoStream;
|
||||
|
||||
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
|
||||
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
|
||||
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
|
||||
bool isEligibleForDirectPlay = options.EnableDirectPlay && IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
|
||||
bool isEligibleForDirectStream = options.EnableDirectStream && IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
|
||||
|
||||
_logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
|
||||
options.Profile.Name ?? "Unknown Profile",
|
||||
|
@ -464,17 +492,37 @@ namespace MediaBrowser.Model.Dlna
|
|||
}
|
||||
playlistItem.SubProtocol = transcodingProfile.Protocol;
|
||||
playlistItem.AudioStreamIndex = audioStreamIndex;
|
||||
ConditionProcessor conditionProcessor = new ConditionProcessor();
|
||||
|
||||
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
|
||||
foreach (CodecProfile i in options.Profile.CodecProfiles)
|
||||
{
|
||||
if (i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec, transcodingProfile.Container))
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
bool applyConditions = true;
|
||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||
{
|
||||
videoTranscodingConditions.Add(c);
|
||||
bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream);
|
||||
int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate;
|
||||
int? audioChannels = audioStream == null ? null : audioStream.Channels;
|
||||
string audioProfile = audioStream == null ? null : audioStream.Profile;
|
||||
|
||||
if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, audioProfile, isSecondaryAudio))
|
||||
{
|
||||
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
||||
applyConditions = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applyConditions)
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
{
|
||||
videoTranscodingConditions.Add(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
ApplyTranscodingConditions(playlistItem, videoTranscodingConditions);
|
||||
|
@ -484,11 +532,42 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(playlistItem.TargetAudioCodec, transcodingProfile.Container))
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
bool applyConditions = true;
|
||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||
{
|
||||
audioTranscodingConditions.Add(c);
|
||||
int? width = videoStream == null ? null : videoStream.Width;
|
||||
int? height = videoStream == null ? null : videoStream.Height;
|
||||
int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
|
||||
int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
|
||||
double? videoLevel = videoStream == null ? null : videoStream.Level;
|
||||
string videoProfile = videoStream == null ? null : videoStream.Profile;
|
||||
float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate;
|
||||
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
|
||||
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
|
||||
|
||||
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
|
||||
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
|
||||
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
|
||||
|
||||
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
|
||||
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
|
||||
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
|
||||
{
|
||||
LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item);
|
||||
applyConditions = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applyConditions)
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
{
|
||||
audioTranscodingConditions.Add(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
||||
|
@ -667,9 +746,23 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
if (i.Type == CodecType.Video && i.ContainsCodec(videoCodec, container))
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
bool applyConditions = true;
|
||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||
{
|
||||
conditions.Add(c);
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
|
||||
{
|
||||
LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource);
|
||||
applyConditions = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applyConditions)
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
{
|
||||
conditions.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -698,20 +791,35 @@ namespace MediaBrowser.Model.Dlna
|
|||
}
|
||||
|
||||
conditions = new List<ProfileCondition>();
|
||||
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
|
||||
|
||||
foreach (CodecProfile i in profile.CodecProfiles)
|
||||
{
|
||||
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec, container))
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
bool applyConditions = true;
|
||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||
{
|
||||
conditions.Add(c);
|
||||
if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
|
||||
{
|
||||
LogConditionFailure(profile, "VideoAudioCodecProfile", applyCondition, mediaSource);
|
||||
applyConditions = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applyConditions)
|
||||
{
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
{
|
||||
conditions.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ProfileCondition i in conditions)
|
||||
{
|
||||
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
|
||||
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
|
||||
{
|
||||
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Dto
|
|||
/// This holds information about a BaseItem in a format that is convenient for the client.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Name = {Name}, ID = {Id}, Type = {Type}")]
|
||||
public class BaseItemDto : IHasProviderIds, IHasPropertyChangedEvent, IItemDto, IHasServerId, IHasSyncInfo
|
||||
public class BaseItemDto : IHasProviderIds, IItemDto, IHasServerId, IHasSyncInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
|
@ -114,6 +114,8 @@ namespace MediaBrowser.Model.Dto
|
|||
/// <value>The synchronize percent.</value>
|
||||
public double? SyncPercent { get; set; }
|
||||
|
||||
public string Container { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DVD season number.
|
||||
/// </summary>
|
||||
|
@ -954,6 +956,16 @@ namespace MediaBrowser.Model.Dto
|
|||
get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Thumb); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has thumb.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance has thumb; otherwise, <c>false</c>.</value>
|
||||
[IgnoreDataMember]
|
||||
public bool HasBackdrop
|
||||
{
|
||||
get { return (BackdropImageTags != null && BackdropImageTags.Count > 0) || (ParentBackdropImageTags != null && ParentBackdropImageTags.Count > 0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has primary image.
|
||||
/// </summary>
|
||||
|
@ -1099,11 +1111,6 @@ namespace MediaBrowser.Model.Dto
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [property changed].
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the program identifier.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using MediaBrowser.Model.Extensions;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
|
@ -9,7 +8,7 @@ namespace MediaBrowser.Model.Dto
|
|||
/// This is used by the api to get information about a Person within a BaseItem
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")]
|
||||
public class BaseItemPerson : IHasPropertyChangedEvent
|
||||
public class BaseItemPerson
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
|
@ -53,10 +52,5 @@ namespace MediaBrowser.Model.Dto
|
|||
return PrimaryImageTag != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [property changed].
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Serialization;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace MediaBrowser.Model.Dto
|
||||
{
|
||||
|
@ -9,7 +7,7 @@ namespace MediaBrowser.Model.Dto
|
|||
/// Class ChapterInfo
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Name = {Name}")]
|
||||
public class ChapterInfoDto : IHasPropertyChangedEvent
|
||||
public class ChapterInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the start position ticks.
|
||||
|
@ -38,7 +36,5 @@ namespace MediaBrowser.Model.Dto
|
|||
{
|
||||
get { return ImageTag != null; }
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Connect;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Users;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
@ -13,7 +12,7 @@ namespace MediaBrowser.Model.Dto
|
|||
/// Class UserDto
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Name = {Name}, ID = {Id}, HasPassword = {HasPassword}")]
|
||||
public class UserDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
|
||||
public class UserDto : IItemDto, IHasServerId
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
|
@ -141,11 +140,6 @@ namespace MediaBrowser.Model.Dto
|
|||
Policy = new UserPolicy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [property changed].
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name ?? base.ToString();
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using MediaBrowser.Model.Extensions;
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MediaBrowser.Model.Dto
|
||||
|
@ -7,7 +6,7 @@ namespace MediaBrowser.Model.Dto
|
|||
/// <summary>
|
||||
/// Class UserItemDataDto
|
||||
/// </summary>
|
||||
public class UserItemDataDto : IHasPropertyChangedEvent
|
||||
public class UserItemDataDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the rating.
|
||||
|
@ -74,7 +73,5 @@ namespace MediaBrowser.Model.Dto
|
|||
/// </summary>
|
||||
/// <value>The item identifier.</value>
|
||||
public string ItemId { get; set; }
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Model.Entities
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -23,5 +24,6 @@ namespace MediaBrowser.Model.Entities
|
|||
/// </summary>
|
||||
/// <value>The image path.</value>
|
||||
public string ImagePath { get; set; }
|
||||
public DateTime ImageDateModified { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
using MediaBrowser.Model.Drawing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace MediaBrowser.Model.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the display preferences for any item that supports them (usually Folders)
|
||||
/// </summary>
|
||||
public class DisplayPreferences : IHasPropertyChangedEvent
|
||||
public class DisplayPreferences
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when [property changed].
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The image scale
|
||||
/// </summary>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace MediaBrowser.Model.Extensions
|
||||
{
|
||||
public interface IHasPropertyChangedEvent : INotifyPropertyChanged
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,17 +1,10 @@
|
|||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MediaBrowser.Model.LiveTv
|
||||
{
|
||||
public class BaseTimerInfoDto : IHasPropertyChangedEvent, IHasServerId
|
||||
public class BaseTimerInfoDto : IHasServerId
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Id of the recording.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Library;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
@ -13,7 +12,7 @@ namespace MediaBrowser.Model.LiveTv
|
|||
/// Class ChannelInfoDto
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Name = {Name}, Number = {Number}")]
|
||||
public class ChannelInfoDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
|
||||
public class ChannelInfoDto : IItemDto, IHasServerId
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
|
@ -120,7 +119,5 @@ namespace MediaBrowser.Model.LiveTv
|
|||
ImageTags = new Dictionary<ImageType, string>();
|
||||
MediaSources = new List<MediaSourceInfo>();
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,11 @@ namespace MediaBrowser.Model.LiveTv
|
|||
{
|
||||
public class TimerInfoDto : BaseTimerInfoDto
|
||||
{
|
||||
public TimerInfoDto()
|
||||
{
|
||||
Type = "Timer";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status.
|
||||
/// </summary>
|
||||
|
@ -22,6 +27,8 @@ namespace MediaBrowser.Model.LiveTv
|
|||
/// <value>The external series timer identifier.</value>
|
||||
public string ExternalSeriesTimerId { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the run time ticks.
|
||||
/// </summary>
|
||||
|
|
|
@ -229,7 +229,6 @@
|
|||
<Compile Include="Entities\SortOrder.cs" />
|
||||
<Compile Include="Events\GenericEventArgs.cs" />
|
||||
<Compile Include="Extensions\DoubleHelper.cs" />
|
||||
<Compile Include="Extensions\IHasPropertyChangedEvent.cs" />
|
||||
<Compile Include="Extensions\IntHelper.cs" />
|
||||
<Compile Include="Extensions\ListHelper.cs" />
|
||||
<Compile Include="Extensions\StringHelper.cs" />
|
||||
|
|
|
@ -197,6 +197,8 @@
|
|||
/// </summary>
|
||||
SeriesGenres,
|
||||
|
||||
SeriesPrimaryImage,
|
||||
|
||||
/// <summary>
|
||||
/// The series studio
|
||||
/// </summary>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
@ -8,7 +7,7 @@ using System.Diagnostics;
|
|||
namespace MediaBrowser.Model.Session
|
||||
{
|
||||
[DebuggerDisplay("Client = {Client}, Username = {UserName}")]
|
||||
public class SessionInfoDto : IHasPropertyChangedEvent
|
||||
public class SessionInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the supported commands.
|
||||
|
@ -116,8 +115,6 @@ namespace MediaBrowser.Model.Session
|
|||
|
||||
public TranscodingInfo TranscodingInfo { get; set; }
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public SessionInfoDto()
|
||||
{
|
||||
AdditionalUsers = new List<SessionUserInfo>();
|
||||
|
|
|
@ -72,7 +72,10 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
// Try to translate to three character code
|
||||
// Be flexible and check against both the full and three character versions
|
||||
var culture = _localization.GetCultures()
|
||||
.FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
|
||||
.FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (culture != null)
|
||||
{
|
||||
|
@ -99,10 +102,12 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
private string NormalizeFilenameForSubtitleComparison(string filename)
|
||||
{
|
||||
// Try to account for sloppy file naming
|
||||
filename = filename.Replace("-", string.Empty);
|
||||
filename = filename.Replace("_", string.Empty);
|
||||
filename = filename.Replace(" ", string.Empty);
|
||||
|
||||
// can't normalize this due to languages such as pt-br
|
||||
//filename = filename.Replace("-", string.Empty);
|
||||
|
||||
//filename = filename.Replace(".", string.Empty);
|
||||
|
||||
return filename;
|
||||
|
|
|
@ -111,7 +111,8 @@ namespace MediaBrowser.Providers.TV
|
|||
Name = seasonName,
|
||||
IndexNumber = seasonNumber,
|
||||
Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)),
|
||||
IsVirtualItem = isVirtualItem
|
||||
IsVirtualItem = isVirtualItem,
|
||||
SeriesId = series.Id
|
||||
};
|
||||
|
||||
season.SetParent(series);
|
||||
|
|
|
@ -429,7 +429,9 @@ namespace MediaBrowser.Providers.TV
|
|||
IndexNumber = episodeNumber,
|
||||
ParentIndexNumber = seasonNumber,
|
||||
Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)),
|
||||
IsVirtualItem = true
|
||||
IsVirtualItem = true,
|
||||
SeasonId = season == null ? (Guid?)null : season.Id,
|
||||
SeriesId = series.Id
|
||||
};
|
||||
|
||||
episode.SetParent(season);
|
||||
|
|
|
@ -95,13 +95,9 @@ namespace MediaBrowser.Server.Implementations.Configuration
|
|||
{
|
||||
metadataPath = GetInternalMetadataPath();
|
||||
}
|
||||
else if (Configuration.EnableCustomPathSubFolders)
|
||||
{
|
||||
metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
|
||||
}
|
||||
else
|
||||
{
|
||||
metadataPath = Configuration.MetadataPath;
|
||||
metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
|
||||
}
|
||||
|
||||
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
|
||||
|
|
|
@ -663,29 +663,11 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
dto.GameSystem = item.GameSystemName;
|
||||
}
|
||||
|
||||
private List<string> GetBackdropImageTags(BaseItem item, int limit)
|
||||
private List<string> GetImageTags(BaseItem item, List<ItemImageInfo> images)
|
||||
{
|
||||
return GetCacheTags(item, ImageType.Backdrop, limit).ToList();
|
||||
}
|
||||
|
||||
private List<string> GetScreenshotImageTags(BaseItem item, int limit)
|
||||
{
|
||||
var hasScreenshots = item as IHasScreenshots;
|
||||
if (hasScreenshots == null)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
return GetCacheTags(item, ImageType.Screenshot, limit).ToList();
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit)
|
||||
{
|
||||
return item.GetImages(type)
|
||||
// Convert to a list now in case GetImageCacheTag is slow
|
||||
.ToList()
|
||||
return images
|
||||
.Select(p => GetImageCacheTag(item, p))
|
||||
.Where(i => i != null)
|
||||
.Take(limit)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
@ -850,53 +832,6 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If an item does not any backdrops, this can be used to find the first parent that does have one
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="owner">The owner.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
private BaseItem GetParentBackdropItem(BaseItem item, BaseItem owner)
|
||||
{
|
||||
var parent = item.GetParent() ?? owner;
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent.GetImages(ImageType.Backdrop).Any())
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent = parent.GetParent();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If an item does not have a logo, this can be used to find the first parent that does have one
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="owner">The owner.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
private BaseItem GetParentImageItem(BaseItem item, ImageType type, BaseItem owner)
|
||||
{
|
||||
var parent = item.GetParent() ?? owner;
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent.HasImage(type))
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent = parent.GetParent();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chapter info dto.
|
||||
/// </summary>
|
||||
|
@ -917,7 +852,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
{
|
||||
Path = chapterInfo.ImagePath,
|
||||
Type = ImageType.Chapter,
|
||||
DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath)
|
||||
DateModified = chapterInfo.ImageDateModified
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -958,6 +893,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
dto.LockData = item.IsLocked;
|
||||
dto.ForcedSortName = item.ForcedSortName;
|
||||
}
|
||||
dto.Container = item.Container;
|
||||
|
||||
var hasBudget = item as IHasBudget;
|
||||
if (hasBudget != null)
|
||||
|
@ -1027,7 +963,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
|
||||
if (backdropLimit > 0)
|
||||
{
|
||||
dto.BackdropImageTags = GetBackdropImageTags(item, backdropLimit);
|
||||
dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
|
||||
}
|
||||
|
||||
if (fields.Contains(ItemFields.ScreenshotImageTags))
|
||||
|
@ -1035,7 +971,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
|
||||
if (screenshotLimit > 0)
|
||||
{
|
||||
dto.ScreenshotImageTags = GetScreenshotImageTags(item, screenshotLimit);
|
||||
dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1064,6 +1000,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
|
||||
dto.Id = GetDtoId(item);
|
||||
dto.IndexNumber = item.IndexNumber;
|
||||
dto.ParentIndexNumber = item.ParentIndexNumber;
|
||||
dto.IsFolder = item.IsFolder;
|
||||
dto.MediaType = item.MediaType;
|
||||
dto.LocationType = item.LocationType;
|
||||
|
@ -1076,15 +1013,11 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
|
||||
dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
|
||||
|
||||
var hasCriticRating = item as IHasCriticRating;
|
||||
if (hasCriticRating != null)
|
||||
{
|
||||
dto.CriticRating = hasCriticRating.CriticRating;
|
||||
dto.CriticRating = item.CriticRating;
|
||||
|
||||
if (fields.Contains(ItemFields.CriticRatingSummary))
|
||||
{
|
||||
dto.CriticRatingSummary = hasCriticRating.CriticRatingSummary;
|
||||
}
|
||||
if (fields.Contains(ItemFields.CriticRatingSummary))
|
||||
{
|
||||
dto.CriticRatingSummary = item.CriticRatingSummary;
|
||||
}
|
||||
|
||||
var hasTrailers = item as IHasTrailers;
|
||||
|
@ -1127,23 +1060,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
|
||||
if (fields.Contains(ItemFields.ShortOverview))
|
||||
{
|
||||
var hasShortOverview = item as IHasShortOverview;
|
||||
if (hasShortOverview != null)
|
||||
{
|
||||
dto.ShortOverview = hasShortOverview.ShortOverview;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance
|
||||
if (backdropLimit > 0 && dto.BackdropImageTags.Count == 0)
|
||||
{
|
||||
var parentWithBackdrop = GetParentBackdropItem(item, owner);
|
||||
|
||||
if (parentWithBackdrop != null)
|
||||
{
|
||||
dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop);
|
||||
dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop, backdropLimit);
|
||||
}
|
||||
dto.ShortOverview = item.ShortOverview;
|
||||
}
|
||||
|
||||
if (fields.Contains(ItemFields.ParentId))
|
||||
|
@ -1155,46 +1072,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
}
|
||||
}
|
||||
|
||||
dto.ParentIndexNumber = item.ParentIndexNumber;
|
||||
|
||||
// If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance
|
||||
if (!dto.HasLogo && options.GetImageLimit(ImageType.Logo) > 0)
|
||||
{
|
||||
var parentWithLogo = GetParentImageItem(item, ImageType.Logo, owner);
|
||||
|
||||
if (parentWithLogo != null)
|
||||
{
|
||||
dto.ParentLogoItemId = GetDtoId(parentWithLogo);
|
||||
|
||||
dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no art, indicate what parent has one in case the Ui wants to allow inheritance
|
||||
if (!dto.HasArtImage && options.GetImageLimit(ImageType.Art) > 0)
|
||||
{
|
||||
var parentWithImage = GetParentImageItem(item, ImageType.Art, owner);
|
||||
|
||||
if (parentWithImage != null)
|
||||
{
|
||||
dto.ParentArtItemId = GetDtoId(parentWithImage);
|
||||
|
||||
dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no thumb, indicate what parent has one in case the Ui wants to allow inheritance
|
||||
if (!dto.HasThumb && options.GetImageLimit(ImageType.Thumb) > 0)
|
||||
{
|
||||
var parentWithImage = GetParentImageItem(item, ImageType.Thumb, owner);
|
||||
|
||||
if (parentWithImage != null)
|
||||
{
|
||||
dto.ParentThumbItemId = GetDtoId(parentWithImage);
|
||||
|
||||
dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb);
|
||||
}
|
||||
}
|
||||
AddInheritedImages(dto, item, options, owner);
|
||||
|
||||
if (fields.Contains(ItemFields.Path))
|
||||
{
|
||||
|
@ -1426,42 +1304,38 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
dto.SeasonId = seasonId.Value.ToString("N");
|
||||
}
|
||||
|
||||
var episodeSeason = episode.Season;
|
||||
if (episodeSeason != null)
|
||||
dto.SeasonName = episode.SeasonName;
|
||||
|
||||
var seriesId = episode.SeriesId;
|
||||
if (seriesId.HasValue)
|
||||
{
|
||||
if (fields.Contains(ItemFields.SeasonName))
|
||||
{
|
||||
dto.SeasonName = episodeSeason.Name;
|
||||
}
|
||||
dto.SeriesId = seriesId.Value.ToString("N");
|
||||
}
|
||||
|
||||
var episodeSeries = episode.Series;
|
||||
Series episodeSeries = null;
|
||||
|
||||
if (episodeSeries != null)
|
||||
if (fields.Contains(ItemFields.SeriesGenres))
|
||||
{
|
||||
if (fields.Contains(ItemFields.SeriesGenres))
|
||||
episodeSeries = episodeSeries ?? episode.Series;
|
||||
if (episodeSeries != null)
|
||||
{
|
||||
dto.SeriesGenres = episodeSeries.Genres.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
dto.SeriesId = GetDtoId(episodeSeries);
|
||||
|
||||
if (fields.Contains(ItemFields.AirTime))
|
||||
{
|
||||
dto.AirTime = episodeSeries.AirTime;
|
||||
}
|
||||
|
||||
if (options.GetImageLimit(ImageType.Thumb) > 0)
|
||||
{
|
||||
dto.SeriesThumbImageTag = GetImageCacheTag(episodeSeries, ImageType.Thumb);
|
||||
}
|
||||
|
||||
if (options.GetImageLimit(ImageType.Primary) > 0)
|
||||
//if (fields.Contains(ItemFields.SeriesPrimaryImage))
|
||||
{
|
||||
episodeSeries = episodeSeries ?? episode.Series;
|
||||
if (episodeSeries != null)
|
||||
{
|
||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.Contains(ItemFields.SeriesStudio))
|
||||
if (fields.Contains(ItemFields.SeriesStudio))
|
||||
{
|
||||
episodeSeries = episodeSeries ?? episode.Series;
|
||||
if (episodeSeries != null)
|
||||
{
|
||||
dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
|
||||
}
|
||||
|
@ -1483,16 +1357,29 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
var season = item as Season;
|
||||
if (season != null)
|
||||
{
|
||||
series = season.Series;
|
||||
dto.SeriesName = season.SeriesName;
|
||||
|
||||
if (series != null)
|
||||
var seriesId = season.SeriesId;
|
||||
if (seriesId.HasValue)
|
||||
{
|
||||
dto.SeriesId = GetDtoId(series);
|
||||
dto.SeriesName = series.Name;
|
||||
dto.AirTime = series.AirTime;
|
||||
dto.SeriesStudio = series.Studios.FirstOrDefault();
|
||||
dto.SeriesId = seriesId.Value.ToString("N");
|
||||
}
|
||||
|
||||
if (options.GetImageLimit(ImageType.Primary) > 0)
|
||||
series = null;
|
||||
|
||||
if (fields.Contains(ItemFields.SeriesStudio))
|
||||
{
|
||||
series = series ?? season.Series;
|
||||
if (series != null)
|
||||
{
|
||||
dto.SeriesStudio = series.Studios.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.Contains(ItemFields.SeriesPrimaryImage))
|
||||
{
|
||||
series = series ?? season.Series;
|
||||
if (series != null)
|
||||
{
|
||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
|
||||
}
|
||||
|
@ -1543,6 +1430,77 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
}
|
||||
}
|
||||
|
||||
private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem owner)
|
||||
{
|
||||
var logoLimit = options.GetImageLimit(ImageType.Logo);
|
||||
var artLimit = options.GetImageLimit(ImageType.Art);
|
||||
var thumbLimit = options.GetImageLimit(ImageType.Thumb);
|
||||
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
|
||||
|
||||
if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BaseItem parent = null;
|
||||
var isFirst = true;
|
||||
|
||||
while (((!dto.HasLogo && logoLimit > 0) || (!dto.HasArtImage && artLimit > 0) || (!dto.HasThumb && thumbLimit > 0) || parent is Series) &&
|
||||
(parent = parent ?? (isFirst ? item.GetParent() ?? owner : parent)) != null)
|
||||
{
|
||||
if (parent == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var allImages = parent.ImageInfos;
|
||||
|
||||
if (logoLimit > 0 && !dto.HasLogo && dto.ParentLogoItemId == null)
|
||||
{
|
||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentLogoItemId = GetDtoId(parent);
|
||||
dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
|
||||
}
|
||||
}
|
||||
if (artLimit > 0 && !dto.HasArtImage && dto.ParentArtItemId == null)
|
||||
{
|
||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentArtItemId = GetDtoId(parent);
|
||||
dto.ParentArtImageTag = GetImageCacheTag(parent, image);
|
||||
}
|
||||
}
|
||||
if (thumbLimit > 0 && !dto.HasThumb && (dto.ParentThumbItemId == null || parent is Series))
|
||||
{
|
||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentThumbItemId = GetDtoId(parent);
|
||||
dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
|
||||
}
|
||||
}
|
||||
if (backdropLimit > 0 && !dto.HasBackdrop)
|
||||
{
|
||||
var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
|
||||
|
||||
if (images.Count > 0)
|
||||
{
|
||||
dto.ParentBackdropItemId = GetDtoId(parent);
|
||||
dto.ParentBackdropImageTags = GetImageTags(parent, images);
|
||||
}
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
parent = parent.GetParent();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetMappedPath(IHasMetadata item)
|
||||
{
|
||||
var path = item.Path;
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using ServiceStack;
|
||||
using ServiceStack.Web;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.HttpServer
|
||||
{
|
||||
public class AsyncStreamWriterFunc : IStreamWriter, IAsyncStreamWriter, IHasOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the source stream.
|
||||
/// </summary>
|
||||
/// <value>The source stream.</value>
|
||||
private Func<Stream, Task> Writer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the options.
|
||||
/// </summary>
|
||||
/// <value>The options.</value>
|
||||
public IDictionary<string, string> Options { get; private set; }
|
||||
|
||||
public Action OnComplete { get; set; }
|
||||
public Action OnError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
|
||||
/// </summary>
|
||||
public AsyncStreamWriterFunc(Func<Stream, Task> writer, IDictionary<string, string> headers)
|
||||
{
|
||||
Writer = writer;
|
||||
|
||||
if (headers == null)
|
||||
{
|
||||
headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
Options = headers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to.
|
||||
/// </summary>
|
||||
/// <param name="responseStream">The response stream.</param>
|
||||
public void WriteTo(Stream responseStream)
|
||||
{
|
||||
var task = Writer(responseStream);
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
|
||||
public async Task WriteToAsync(Stream responseStream)
|
||||
{
|
||||
await Writer(responseStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -335,7 +335,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
/// <param name="httpReq">The HTTP req.</param>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <returns>Task.</returns>
|
||||
protected Task RequestHandler(IHttpRequest httpReq, Uri url)
|
||||
protected async Task RequestHandler(IHttpRequest httpReq, Uri url)
|
||||
{
|
||||
var date = DateTime.Now;
|
||||
|
||||
|
@ -345,7 +345,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
{
|
||||
httpRes.StatusCode = 503;
|
||||
httpRes.Close();
|
||||
return Task.FromResult(true);
|
||||
return ;
|
||||
}
|
||||
|
||||
var operationName = httpReq.OperationName;
|
||||
|
@ -365,13 +365,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpRes.RedirectToUrl(DefaultRedirectPath);
|
||||
return Task.FromResult(true);
|
||||
return;
|
||||
}
|
||||
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpRes.RedirectToUrl("emby/" + DefaultRedirectPath);
|
||||
return Task.FromResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
|
||||
|
@ -389,35 +389,35 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
httpRes.Write("<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>");
|
||||
|
||||
httpRes.Close();
|
||||
return Task.FromResult(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpRes.RedirectToUrl(DefaultRedirectPath);
|
||||
return Task.FromResult(true);
|
||||
return;
|
||||
}
|
||||
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpRes.RedirectToUrl("../" + DefaultRedirectPath);
|
||||
return Task.FromResult(true);
|
||||
return;
|
||||
}
|
||||
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpRes.RedirectToUrl(DefaultRedirectPath);
|
||||
return Task.FromResult(true);
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(localPath))
|
||||
{
|
||||
httpRes.RedirectToUrl("/" + DefaultRedirectPath);
|
||||
return Task.FromResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpRes.RedirectToUrl("web/pin.html");
|
||||
return Task.FromResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(GlobalResponse))
|
||||
|
@ -427,7 +427,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
httpRes.Write(GlobalResponse);
|
||||
|
||||
httpRes.Close();
|
||||
return Task.FromResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var handler = HttpHandlerFactory.GetHandler(httpReq);
|
||||
|
@ -443,13 +443,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName();
|
||||
}
|
||||
|
||||
var task = serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName);
|
||||
|
||||
task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
|
||||
//Matches Exceptions handled in HttpListenerBase.InitTask()
|
||||
|
||||
task.ContinueWith(x =>
|
||||
try
|
||||
{
|
||||
await serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpRes.Close();
|
||||
var statusCode = httpRes.StatusCode;
|
||||
|
||||
var duration = DateTime.Now - date;
|
||||
|
@ -458,13 +458,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
{
|
||||
LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration);
|
||||
}
|
||||
|
||||
}, TaskContinuationOptions.None);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
return new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo)
|
||||
.AsTaskException();
|
||||
throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -331,7 +331,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
options.ContentType = MimeTypes.GetMimeType(path);
|
||||
}
|
||||
|
||||
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
|
||||
if (!options.DateLastModified.HasValue)
|
||||
{
|
||||
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
|
||||
}
|
||||
|
||||
var cacheKey = path + options.DateLastModified.Value.Ticks;
|
||||
|
||||
options.CacheKey = cacheKey.GetMD5();
|
||||
|
@ -699,5 +703,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
|
||||
throw error;
|
||||
}
|
||||
|
||||
public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null)
|
||||
{
|
||||
return new AsyncStreamWriterFunc(streamWriter, responseHeaders);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,285 +0,0 @@
|
|||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using ServiceStack;
|
||||
using ServiceStack.Host.HttpListener;
|
||||
using ServiceStack.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.HttpServer.NetListener
|
||||
{
|
||||
public class HttpListenerServer : IHttpListener
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private HttpListener _listener;
|
||||
private readonly ManualResetEventSlim _listenForNextRequest = new ManualResetEventSlim(false);
|
||||
|
||||
public Action<Exception, IRequest> ErrorHandler { get; set; }
|
||||
public Action<WebSocketConnectEventArgs> WebSocketHandler { get; set; }
|
||||
public Func<IHttpRequest, Uri, Task> RequestHandler { get; set; }
|
||||
|
||||
private readonly Action<string> _endpointListener;
|
||||
|
||||
public HttpListenerServer(ILogger logger, Action<string> endpointListener)
|
||||
{
|
||||
_logger = logger;
|
||||
_endpointListener = endpointListener;
|
||||
}
|
||||
|
||||
private List<string> UrlPrefixes { get; set; }
|
||||
|
||||
public void Start(IEnumerable<string> urlPrefixes)
|
||||
{
|
||||
UrlPrefixes = urlPrefixes.ToList();
|
||||
|
||||
if (_listener == null)
|
||||
_listener = new HttpListener();
|
||||
|
||||
//HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First());
|
||||
|
||||
foreach (var prefix in UrlPrefixes)
|
||||
{
|
||||
_logger.Info("Adding HttpListener prefix " + prefix);
|
||||
_listener.Prefixes.Add(prefix);
|
||||
}
|
||||
|
||||
_listener.Start();
|
||||
|
||||
Task.Factory.StartNew(Listen, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
private bool IsListening
|
||||
{
|
||||
get { return _listener != null && _listener.IsListening; }
|
||||
}
|
||||
|
||||
// Loop here to begin processing of new requests.
|
||||
private void Listen()
|
||||
{
|
||||
while (IsListening)
|
||||
{
|
||||
if (_listener == null) return;
|
||||
_listenForNextRequest.Reset();
|
||||
|
||||
try
|
||||
{
|
||||
_listener.BeginGetContext(ListenerCallback, _listener);
|
||||
_listenForNextRequest.Wait();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("Listen()", ex);
|
||||
return;
|
||||
}
|
||||
if (_listener == null) return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the processing of a request in here.
|
||||
private void ListenerCallback(IAsyncResult asyncResult)
|
||||
{
|
||||
_listenForNextRequest.Set();
|
||||
|
||||
var listener = asyncResult.AsyncState as HttpListener;
|
||||
HttpListenerContext context;
|
||||
|
||||
if (listener == null) return;
|
||||
var isListening = listener.IsListening;
|
||||
|
||||
try
|
||||
{
|
||||
if (!isListening)
|
||||
{
|
||||
_logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); return;
|
||||
}
|
||||
// The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework,
|
||||
// blocks until there is a request to be processed or some type of data is available.
|
||||
context = listener.EndGetContext(asyncResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// You will get an exception when httpListener.Stop() is called
|
||||
// because there will be a thread stopped waiting on the .EndGetContext()
|
||||
// method, and again, that is just the way most Begin/End asynchronous
|
||||
// methods of the .NET Framework work.
|
||||
var errMsg = ex + ": " + IsListening;
|
||||
_logger.Warn(errMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Factory.StartNew(() => InitTask(context));
|
||||
}
|
||||
|
||||
private void InitTask(HttpListenerContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = this.ProcessRequestAsync(context);
|
||||
task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
|
||||
|
||||
if (task.Status == TaskStatus.Created)
|
||||
{
|
||||
task.RunSynchronously();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleError(ex, context);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessRequestAsync(HttpListenerContext context)
|
||||
{
|
||||
var request = context.Request;
|
||||
|
||||
LogHttpRequest(request);
|
||||
|
||||
if (request.IsWebSocketRequest)
|
||||
{
|
||||
return ProcessWebSocketRequest(context);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(context.Request.RawUrl))
|
||||
return ((object)null).AsTaskResult();
|
||||
|
||||
var operationName = context.Request.GetOperationName();
|
||||
|
||||
var httpReq = GetRequest(context, operationName);
|
||||
|
||||
return RequestHandler(httpReq, request.Url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the web socket request.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The CTX.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task ProcessWebSocketRequest(HttpListenerContext ctx)
|
||||
{
|
||||
#if !__MonoCS__
|
||||
try
|
||||
{
|
||||
var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false);
|
||||
|
||||
if (WebSocketHandler != null)
|
||||
{
|
||||
WebSocketHandler(new WebSocketConnectEventArgs
|
||||
{
|
||||
WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger),
|
||||
Endpoint = ctx.Request.RemoteEndPoint.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("AcceptWebSocketAsync error", ex);
|
||||
ctx.Response.StatusCode = 500;
|
||||
ctx.Response.Close();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void HandleError(Exception ex, HttpListenerContext context)
|
||||
{
|
||||
var operationName = context.Request.GetOperationName();
|
||||
var httpReq = GetRequest(context, operationName);
|
||||
|
||||
if (ErrorHandler != null)
|
||||
{
|
||||
ErrorHandler(ex, httpReq);
|
||||
}
|
||||
}
|
||||
|
||||
private static ListenerRequest GetRequest(HttpListenerContext httpContext, string operationName)
|
||||
{
|
||||
var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None);
|
||||
req.RequestAttributes = req.GetAttributes();
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the HTTP request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
private void LogHttpRequest(HttpListenerRequest request)
|
||||
{
|
||||
var endpoint = request.LocalEndPoint;
|
||||
|
||||
if (endpoint != null)
|
||||
{
|
||||
var address = endpoint.ToString();
|
||||
|
||||
_endpointListener(address);
|
||||
}
|
||||
|
||||
LogRequest(_logger, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the request.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="request">The request.</param>
|
||||
private static void LogRequest(ILogger logger, HttpListenerRequest request)
|
||||
{
|
||||
var log = new StringBuilder();
|
||||
|
||||
var logHeaders = true;
|
||||
|
||||
if (logHeaders)
|
||||
{
|
||||
var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k]));
|
||||
|
||||
log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers);
|
||||
}
|
||||
|
||||
var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod;
|
||||
|
||||
logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_listener != null)
|
||||
{
|
||||
foreach (var prefix in UrlPrefixes)
|
||||
{
|
||||
_listener.Prefixes.Remove(prefix);
|
||||
}
|
||||
|
||||
_listener.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
private readonly object _disposeLock = new object();
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
lock (_disposeLock)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
//release unmanaged resources here...
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,10 +5,12 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using ServiceStack;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.HttpServer
|
||||
{
|
||||
public class RangeRequestWriter : IStreamWriter, IHttpResult
|
||||
public class RangeRequestWriter : IStreamWriter, IAsyncStreamWriter, IHttpResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the source stream.
|
||||
|
@ -168,16 +170,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
/// </summary>
|
||||
/// <param name="responseStream">The response stream.</param>
|
||||
public void WriteTo(Stream responseStream)
|
||||
{
|
||||
WriteToInternal(responseStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to async.
|
||||
/// </summary>
|
||||
/// <param name="responseStream">The response stream.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private void WriteToInternal(Stream responseStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -237,6 +229,66 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
}
|
||||
}
|
||||
|
||||
public async Task WriteToAsync(Stream responseStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Headers only
|
||||
if (IsHeadRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (var source = SourceStream)
|
||||
{
|
||||
// If the requested range is "0-", we can optimize by just doing a stream copy
|
||||
if (RangeEnd >= TotalContentLength - 1)
|
||||
{
|
||||
await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error in range request writer", ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (OnComplete != null)
|
||||
{
|
||||
OnComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
|
||||
{
|
||||
var array = new byte[BufferSize];
|
||||
int count;
|
||||
while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
var bytesToCopy = Math.Min(count, copyLength);
|
||||
|
||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
|
||||
|
||||
copyLength -= bytesToCopy;
|
||||
|
||||
if (copyLength <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public IRequest RequestContext { get; set; }
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Specialized;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using ServiceStack;
|
||||
using ServiceStack.Web;
|
||||
|
@ -32,53 +33,54 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
|||
return header.Substring(ap + 1, end - ap - 1);
|
||||
}
|
||||
|
||||
void LoadMultiPart()
|
||||
async Task LoadMultiPart()
|
||||
{
|
||||
string boundary = GetParameter(ContentType, "; boundary=");
|
||||
if (boundary == null)
|
||||
return;
|
||||
|
||||
var input = GetSubStream(InputStream);
|
||||
|
||||
//DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
|
||||
//Not ending with \r\n?
|
||||
var ms = new MemoryStream(32 * 1024);
|
||||
input.CopyTo(ms);
|
||||
input = ms;
|
||||
ms.WriteByte((byte)'\r');
|
||||
ms.WriteByte((byte)'\n');
|
||||
|
||||
input.Position = 0;
|
||||
|
||||
//Uncomment to debug
|
||||
//var content = new StreamReader(ms).ReadToEnd();
|
||||
//Console.WriteLine(boundary + "::" + content);
|
||||
//input.Position = 0;
|
||||
|
||||
var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
|
||||
|
||||
HttpMultipart.Element e;
|
||||
while ((e = multi_part.ReadNextElement()) != null)
|
||||
using (var requestStream = GetSubStream(InputStream))
|
||||
{
|
||||
if (e.Filename == null)
|
||||
{
|
||||
byte[] copy = new byte[e.Length];
|
||||
//DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
|
||||
//Not ending with \r\n?
|
||||
var ms = new MemoryStream(32 * 1024);
|
||||
await requestStream.CopyToAsync(ms).ConfigureAwait(false);
|
||||
|
||||
input.Position = e.Start;
|
||||
input.Read(copy, 0, (int)e.Length);
|
||||
var input = ms;
|
||||
ms.WriteByte((byte)'\r');
|
||||
ms.WriteByte((byte)'\n');
|
||||
|
||||
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
|
||||
}
|
||||
else
|
||||
input.Position = 0;
|
||||
|
||||
//Uncomment to debug
|
||||
//var content = new StreamReader(ms).ReadToEnd();
|
||||
//Console.WriteLine(boundary + "::" + content);
|
||||
//input.Position = 0;
|
||||
|
||||
var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
|
||||
|
||||
HttpMultipart.Element e;
|
||||
while ((e = multi_part.ReadNextElement()) != null)
|
||||
{
|
||||
//
|
||||
// We use a substream, as in 2.x we will support large uploads streamed to disk,
|
||||
//
|
||||
HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
|
||||
files.AddFile(e.Name, sub);
|
||||
if (e.Filename == null)
|
||||
{
|
||||
byte[] copy = new byte[e.Length];
|
||||
|
||||
input.Position = e.Start;
|
||||
input.Read(copy, 0, (int)e.Length);
|
||||
|
||||
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// We use a substream, as in 2.x we will support large uploads streamed to disk,
|
||||
//
|
||||
HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
|
||||
files.AddFile(e.Name, sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
EndSubStream(input);
|
||||
}
|
||||
|
||||
public NameValueCollection Form
|
||||
|
@ -91,10 +93,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
|||
files = new HttpFileCollection();
|
||||
|
||||
if (IsContentType("multipart/form-data", true))
|
||||
LoadMultiPart();
|
||||
else if (
|
||||
IsContentType("application/x-www-form-urlencoded", true))
|
||||
LoadWwwForm();
|
||||
{
|
||||
var task = LoadMultiPart();
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
else if (IsContentType("application/x-www-form-urlencoded", true))
|
||||
{
|
||||
var task = LoadWwwForm();
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
|
||||
form.Protect();
|
||||
}
|
||||
|
@ -220,50 +227,50 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
|||
return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void LoadWwwForm()
|
||||
async Task LoadWwwForm()
|
||||
{
|
||||
using (Stream input = GetSubStream(InputStream))
|
||||
{
|
||||
using (StreamReader s = new StreamReader(input, ContentEncoding))
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
StringBuilder key = new StringBuilder();
|
||||
StringBuilder value = new StringBuilder();
|
||||
int c;
|
||||
await input.CopyToAsync(ms).ConfigureAwait(false);
|
||||
ms.Position = 0;
|
||||
|
||||
while ((c = s.Read()) != -1)
|
||||
using (StreamReader s = new StreamReader(ms, ContentEncoding))
|
||||
{
|
||||
if (c == '=')
|
||||
StringBuilder key = new StringBuilder();
|
||||
StringBuilder value = new StringBuilder();
|
||||
int c;
|
||||
|
||||
while ((c = s.Read()) != -1)
|
||||
{
|
||||
value.Length = 0;
|
||||
while ((c = s.Read()) != -1)
|
||||
if (c == '=')
|
||||
{
|
||||
if (c == '&')
|
||||
value.Length = 0;
|
||||
while ((c = s.Read()) != -1)
|
||||
{
|
||||
if (c == '&')
|
||||
{
|
||||
AddRawKeyValue(key, value);
|
||||
break;
|
||||
}
|
||||
else
|
||||
value.Append((char)c);
|
||||
}
|
||||
if (c == -1)
|
||||
{
|
||||
AddRawKeyValue(key, value);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
else
|
||||
value.Append((char)c);
|
||||
}
|
||||
if (c == -1)
|
||||
{
|
||||
else if (c == '&')
|
||||
AddRawKeyValue(key, value);
|
||||
return;
|
||||
}
|
||||
else
|
||||
key.Append((char)c);
|
||||
}
|
||||
else if (c == '&')
|
||||
if (c == -1)
|
||||
AddRawKeyValue(key, value);
|
||||
else
|
||||
key.Append((char)c);
|
||||
}
|
||||
if (c == -1)
|
||||
AddRawKeyValue(key, value);
|
||||
|
||||
EndSubStream(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,12 +134,89 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
|||
get
|
||||
{
|
||||
return remoteIp ??
|
||||
(remoteIp = XForwardedFor ??
|
||||
(NormalizeIp(XRealIp) ??
|
||||
(remoteIp = (CheckBadChars(XForwardedFor)) ??
|
||||
(NormalizeIp(CheckBadChars(XRealIp)) ??
|
||||
(request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null)));
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 };
|
||||
|
||||
//
|
||||
// CheckBadChars - throws on invalid chars to be not found in header name/value
|
||||
//
|
||||
internal static string CheckBadChars(string name)
|
||||
{
|
||||
if (name == null || name.Length == 0)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
// VALUE check
|
||||
//Trim spaces from both ends
|
||||
name = name.Trim(HttpTrimCharacters);
|
||||
|
||||
//First, check for correctly formed multi-line value
|
||||
//Second, check for absenece of CTL characters
|
||||
int crlf = 0;
|
||||
for (int i = 0; i < name.Length; ++i)
|
||||
{
|
||||
char c = (char)(0x000000ff & (uint)name[i]);
|
||||
switch (crlf)
|
||||
{
|
||||
case 0:
|
||||
if (c == '\r')
|
||||
{
|
||||
crlf = 1;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
// Technically this is bad HTTP. But it would be a breaking change to throw here.
|
||||
// Is there an exploit?
|
||||
crlf = 2;
|
||||
}
|
||||
else if (c == 127 || (c < ' ' && c != '\t'))
|
||||
{
|
||||
throw new ArgumentException("net_WebHeaderInvalidControlChars");
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (c == '\n')
|
||||
{
|
||||
crlf = 2;
|
||||
break;
|
||||
}
|
||||
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||
|
||||
case 2:
|
||||
if (c == ' ' || c == '\t')
|
||||
{
|
||||
crlf = 0;
|
||||
break;
|
||||
}
|
||||
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||
}
|
||||
}
|
||||
if (crlf != 0)
|
||||
{
|
||||
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
internal static bool ContainsNonAsciiChars(string token)
|
||||
{
|
||||
for (int i = 0; i < token.Length; ++i)
|
||||
{
|
||||
if ((token[i] < 0x20) || (token[i] > 0x7e))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private string NormalizeIp(string ip)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ip))
|
||||
|
@ -388,10 +465,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
|||
return stream;
|
||||
}
|
||||
|
||||
static void EndSubStream(Stream stream)
|
||||
{
|
||||
}
|
||||
|
||||
public static string GetHandlerPathIfAny(string listenerUrl)
|
||||
{
|
||||
if (listenerUrl == null) return null;
|
||||
|
|
|
@ -4,13 +4,15 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using ServiceStack;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Class StreamWriter
|
||||
/// </summary>
|
||||
public class StreamWriter : IStreamWriter, IHasOptions
|
||||
public class StreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions
|
||||
{
|
||||
private ILogger Logger { get; set; }
|
||||
|
||||
|
@ -73,24 +75,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
{
|
||||
}
|
||||
|
||||
// 256k
|
||||
private const int BufferSize = 262144;
|
||||
|
||||
/// <summary>
|
||||
/// Writes to.
|
||||
/// </summary>
|
||||
/// <param name="responseStream">The response stream.</param>
|
||||
public void WriteTo(Stream responseStream)
|
||||
{
|
||||
WriteToInternal(responseStream);
|
||||
}
|
||||
|
||||
// 256k
|
||||
private const int BufferSize = 262144;
|
||||
|
||||
/// <summary>
|
||||
/// Writes to async.
|
||||
/// </summary>
|
||||
/// <param name="responseStream">The response stream.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private void WriteToInternal(Stream responseStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -107,7 +99,36 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
{
|
||||
OnError();
|
||||
}
|
||||
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (OnComplete != null)
|
||||
{
|
||||
OnComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteToAsync(Stream responseStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var src = SourceStream)
|
||||
{
|
||||
await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorException("Error streaming data", ex);
|
||||
|
||||
if (OnError != null)
|
||||
{
|
||||
OnError();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
|
|
|
@ -84,7 +84,7 @@ namespace MediaBrowser.Server.Implementations.IO
|
|||
// This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to.
|
||||
// Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds
|
||||
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
|
||||
await Task.Delay(25000).ConfigureAwait(false);
|
||||
await Task.Delay(45000).ConfigureAwait(false);
|
||||
|
||||
string val;
|
||||
_tempIgnoredPaths.TryRemove(path, out val);
|
||||
|
|
|
@ -33,6 +33,7 @@ using System.Net;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Model.Channels;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
@ -353,10 +354,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
|
||||
private void RegisterItem(Guid id, BaseItem item)
|
||||
{
|
||||
if (item.SourceType != SourceType.Library)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (item is IItemByName)
|
||||
{
|
||||
if (!(item is MusicArtist))
|
||||
|
@ -364,14 +361,25 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (item is Photo)
|
||||
|
||||
if (item.IsFolder)
|
||||
{
|
||||
return;
|
||||
if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel))
|
||||
{
|
||||
if (item.SourceType != SourceType.Library)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if (!(item is Folder))
|
||||
//{
|
||||
// return;
|
||||
//}
|
||||
else
|
||||
{
|
||||
if (item is Photo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LibraryItemsCache.AddOrUpdate(id, item, delegate { return item; });
|
||||
}
|
||||
|
||||
|
@ -782,19 +790,19 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
|
||||
public BaseItem FindByPath(string path, bool? isFolder)
|
||||
{
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
Path = path,
|
||||
IsFolder = isFolder
|
||||
};
|
||||
|
||||
// If this returns multiple items it could be tricky figuring out which one is correct.
|
||||
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
|
||||
|
||||
return GetItemIds(query)
|
||||
.Select(GetItemById)
|
||||
.Where(i => i != null)
|
||||
.OrderByDescending(i => i.DateCreated)
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
Path = path,
|
||||
IsFolder = isFolder,
|
||||
SortBy = new[] { ItemSortBy.DateCreated },
|
||||
SortOrder = SortOrder.Descending,
|
||||
Limit = 1
|
||||
};
|
||||
|
||||
return GetItemList(query)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
|
@ -1258,6 +1266,8 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
|
||||
item = RetrieveItem(id);
|
||||
|
||||
//_logger.Debug("GetitemById {0}", id);
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
RegisterItem(item);
|
||||
|
@ -1508,7 +1518,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
UserId = user.Id.ToString("N")
|
||||
|
||||
}, CancellationToken.None).Result;
|
||||
|
||||
|
||||
return channelResult.Items;
|
||||
}
|
||||
|
||||
|
@ -1921,7 +1931,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
|
||||
private string GetContentTypeOverride(string path, bool inherit)
|
||||
{
|
||||
var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && _fileSystem.ContainsSubPath(i.Name, path)));
|
||||
var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && !string.IsNullOrWhiteSpace(i.Name) && _fileSystem.ContainsSubPath(i.Name, path)));
|
||||
if (nameValuePair != null)
|
||||
{
|
||||
return nameValuePair.Value;
|
||||
|
@ -2802,6 +2812,11 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
|
||||
private void RemoveContentTypeOverrides(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
throw new ArgumentNullException("path");
|
||||
}
|
||||
|
||||
var removeList = new List<NameValuePair>();
|
||||
|
||||
foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
|
||||
|
|
|
@ -5,15 +5,19 @@ using MediaBrowser.Model.Entities;
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CommonIO;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
public class PhotoResolver : ItemResolver<Photo>
|
||||
{
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
public PhotoResolver(IImageProcessor imageProcessor)
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
|
||||
{
|
||||
_imageProcessor = imageProcessor;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -23,20 +27,45 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
|||
/// <returns>Trailer.</returns>
|
||||
protected override Photo Resolve(ItemResolveArgs args)
|
||||
{
|
||||
// Must be an image file within a photo collection
|
||||
if (string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) &&
|
||||
!args.IsDirectory &&
|
||||
IsImageFile(args.Path, _imageProcessor))
|
||||
if (!args.IsDirectory)
|
||||
{
|
||||
return new Photo
|
||||
// Must be an image file within a photo collection
|
||||
var collectionType = args.GetCollectionType();
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Path = args.Path
|
||||
};
|
||||
if (IsImageFile(args.Path, _imageProcessor))
|
||||
{
|
||||
var filename = Path.GetFileNameWithoutExtension(args.Path);
|
||||
|
||||
// Make sure the image doesn't belong to a video file
|
||||
if (args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(i, filename)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Photo
|
||||
{
|
||||
Path = args.Path
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsOwnedByMedia(FileSystemMetadata file, string imageFilename)
|
||||
{
|
||||
if (_libraryManager.IsVideoFile(file.FullName) && imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file.Name), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly string[] IgnoreFiles =
|
||||
{
|
||||
"folder",
|
||||
|
@ -44,7 +73,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
|||
"landscape",
|
||||
"fanart",
|
||||
"backdrop",
|
||||
"poster"
|
||||
"poster",
|
||||
"cover"
|
||||
};
|
||||
|
||||
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
|
||||
|
|
|
@ -31,7 +31,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
|||
}
|
||||
|
||||
var season = parent as Season;
|
||||
|
||||
// Just in case the user decided to nest episodes.
|
||||
// Not officially supported but in some cases we can handle it.
|
||||
if (season == null)
|
||||
|
@ -41,10 +40,30 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
|||
|
||||
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
|
||||
// Also handle flat tv folders
|
||||
if (season != null || args.HasParent<Series>() || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
||||
if (season != null ||
|
||||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
|
||||
args.HasParent<Series>())
|
||||
{
|
||||
var episode = ResolveVideo<Episode>(args, false);
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
var series = parent as Series;
|
||||
if (series == null)
|
||||
{
|
||||
series = parent.GetParents().OfType<Series>().FirstOrDefault();
|
||||
}
|
||||
|
||||
if (series != null)
|
||||
{
|
||||
episode.SeriesId = series.Id;
|
||||
}
|
||||
if (season != null)
|
||||
{
|
||||
episode.SeasonId = season.Id;
|
||||
}
|
||||
}
|
||||
|
||||
return episode;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,10 +38,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
|||
if (args.Parent is Series && args.IsDirectory)
|
||||
{
|
||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
||||
|
||||
var series = ((Series)args.Parent);
|
||||
|
||||
var season = new Season
|
||||
{
|
||||
IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber
|
||||
IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber,
|
||||
SeriesId = series.Id
|
||||
};
|
||||
|
||||
if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0)
|
||||
|
|
|
@ -1214,8 +1214,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
|||
var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolderId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
list.Add(item);
|
||||
|
||||
_libraryManager.RegisterItem(item);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
|
|
@ -17,6 +17,7 @@ using System.Threading.Tasks;
|
|||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
|
@ -106,18 +107,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
|
||||
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var stream = await _httpClient.Get(new HttpRequestOptions()
|
||||
try
|
||||
{
|
||||
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
|
||||
CancellationToken = cancellationToken,
|
||||
CacheLength = TimeSpan.FromDays(1),
|
||||
CacheMode = CacheMode.Unconditional,
|
||||
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
|
||||
}))
|
||||
{
|
||||
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
||||
using (var stream = await _httpClient.Get(new HttpRequestOptions()
|
||||
{
|
||||
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
|
||||
CancellationToken = cancellationToken,
|
||||
CacheLength = TimeSpan.FromDays(1),
|
||||
CacheMode = CacheMode.Unconditional,
|
||||
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
|
||||
}))
|
||||
{
|
||||
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
||||
|
||||
return response.ModelNumber;
|
||||
return response.ModelNumber;
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
// HDHR4 doesn't have this api
|
||||
return "HDHR";
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,16 +469,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
return;
|
||||
}
|
||||
|
||||
// Test it by pulling down the lineup
|
||||
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
||||
try
|
||||
{
|
||||
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
|
||||
CancellationToken = CancellationToken.None
|
||||
}))
|
||||
{
|
||||
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
||||
// Test it by pulling down the lineup
|
||||
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
||||
{
|
||||
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
|
||||
CancellationToken = CancellationToken.None
|
||||
}))
|
||||
{
|
||||
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
||||
|
||||
info.DeviceId = response.DeviceID;
|
||||
info.DeviceId = response.DeviceID;
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
// HDHR4 doesn't have this api
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,8 +56,8 @@
|
|||
<Reference Include="Interfaces.IO">
|
||||
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MediaBrowser.Naming, Version=1.0.6012.15754, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.52\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
|
||||
<Reference Include="MediaBrowser.Naming, Version=1.0.6046.32295, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.53\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MoreLinq">
|
||||
|
@ -73,8 +73,8 @@
|
|||
<HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SocketHttpListener, Version=1.0.5955.1537, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SocketHttpListener.1.0.0.30\lib\net45\SocketHttpListener.dll</HintPath>
|
||||
<Reference Include="SocketHttpListener, Version=1.0.6046.26351, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SocketHttpListener.1.0.0.35\lib\net45\SocketHttpListener.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
|
@ -156,6 +156,7 @@
|
|||
<Compile Include="EntryPoints\ServerEventNotifier.cs" />
|
||||
<Compile Include="EntryPoints\UserDataChangeNotifier.cs" />
|
||||
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
|
||||
<Compile Include="HttpServer\AsyncStreamWriterFunc.cs" />
|
||||
<Compile Include="HttpServer\IHttpListener.cs" />
|
||||
<Compile Include="HttpServer\Security\AuthorizationContext.cs" />
|
||||
<Compile Include="HttpServer\ContainerAdapter.cs" />
|
||||
|
@ -757,9 +758,7 @@
|
|||
<EmbeddedResource Include="Localization\iso6392.txt" />
|
||||
<EmbeddedResource Include="Localization\Ratings\be.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="HttpServer\NetListener\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
|
|
@ -152,6 +152,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|||
}
|
||||
|
||||
chapter.ImagePath = path;
|
||||
chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
|
||||
changesMade = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -170,6 +171,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|||
else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
chapter.ImagePath = path;
|
||||
chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
|
||||
changesMade = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
|
||||
_logger.Debug("Upgrading schema for {0} items", numItems);
|
||||
|
||||
var list = new List<BaseItem>();
|
||||
|
||||
foreach (var itemId in itemIds)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
@ -166,27 +168,50 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
|
||||
if (item != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error saving item", ex);
|
||||
}
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (list.Count >= 1000)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error saving item", ex);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= numItems;
|
||||
progress.Report(percent * 100);
|
||||
}
|
||||
|
||||
if (list.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error saving item", ex);
|
||||
}
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
private IDbCommand _updateInheritedRatingCommand;
|
||||
private IDbCommand _updateInheritedTagsCommand;
|
||||
|
||||
public const int LatestSchemaVersion = 97;
|
||||
public const int LatestSchemaVersion = 108;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
|
||||
|
@ -269,10 +269,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
_connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT");
|
||||
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesName", "Text");
|
||||
_connection.AddColumn(Logger, "TypedBaseItems", "UserDataKey", "Text");
|
||||
_connection.AddColumn(Logger, "TypedBaseItems", "SeasonName", "Text");
|
||||
_connection.AddColumn(Logger, "TypedBaseItems", "SeasonId", "GUID");
|
||||
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesId", "GUID");
|
||||
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesSortName", "Text");
|
||||
|
||||
_connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
|
||||
_connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text");
|
||||
|
||||
_connection.AddColumn(Logger, ChaptersTableName, "ImageDateModified", "DATETIME");
|
||||
|
||||
string[] postQueries =
|
||||
|
||||
{
|
||||
|
@ -403,7 +409,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
"Album",
|
||||
"CriticRating",
|
||||
"CriticRatingSummary",
|
||||
"IsVirtualItem"
|
||||
"IsVirtualItem",
|
||||
"SeriesName",
|
||||
"SeasonName",
|
||||
"SeasonId",
|
||||
"SeriesId",
|
||||
"SeriesSortName"
|
||||
};
|
||||
|
||||
private readonly string[] _mediaStreamSaveColumns =
|
||||
|
@ -523,7 +534,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
"Album",
|
||||
"IsVirtualItem",
|
||||
"SeriesName",
|
||||
"UserDataKey"
|
||||
"UserDataKey",
|
||||
"SeasonName",
|
||||
"SeasonId",
|
||||
"SeriesId",
|
||||
"SeriesSortName"
|
||||
};
|
||||
_saveItemCommand = _connection.CreateCommand();
|
||||
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
|
||||
|
@ -582,6 +597,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@StartPositionTicks");
|
||||
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@Name");
|
||||
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImagePath");
|
||||
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImageDateModified");
|
||||
|
||||
// MediaStreams
|
||||
_deleteStreamsCommand = _connection.CreateCommand();
|
||||
|
@ -945,7 +961,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
var hasSeries = item as IHasSeries;
|
||||
if (hasSeries != null)
|
||||
{
|
||||
_saveItemCommand.GetParameter(index++).Value = hasSeries.SeriesName;
|
||||
_saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesName();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -954,6 +970,29 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
|
||||
_saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault();
|
||||
|
||||
var episode = item as Episode;
|
||||
if (episode != null)
|
||||
{
|
||||
_saveItemCommand.GetParameter(index++).Value = episode.FindSeasonName();
|
||||
_saveItemCommand.GetParameter(index++).Value = episode.FindSeasonId();
|
||||
}
|
||||
else
|
||||
{
|
||||
_saveItemCommand.GetParameter(index++).Value = null;
|
||||
_saveItemCommand.GetParameter(index++).Value = null;
|
||||
}
|
||||
|
||||
if (hasSeries != null)
|
||||
{
|
||||
_saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesId();
|
||||
_saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesSortName();
|
||||
}
|
||||
else
|
||||
{
|
||||
_saveItemCommand.GetParameter(index++).Value = null;
|
||||
_saveItemCommand.GetParameter(index++).Value = null;
|
||||
}
|
||||
|
||||
_saveItemCommand.Transaction = transaction;
|
||||
|
||||
_saveItemCommand.ExecuteNonQuery();
|
||||
|
@ -1376,6 +1415,44 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
item.IsVirtualItem = reader.GetBoolean(58);
|
||||
}
|
||||
|
||||
var hasSeries = item as IHasSeries;
|
||||
if (hasSeries != null)
|
||||
{
|
||||
if (!reader.IsDBNull(59))
|
||||
{
|
||||
hasSeries.SeriesName = reader.GetString(59);
|
||||
}
|
||||
}
|
||||
|
||||
var episode = item as Episode;
|
||||
if (episode != null)
|
||||
{
|
||||
if (!reader.IsDBNull(60))
|
||||
{
|
||||
episode.SeasonName = reader.GetString(60);
|
||||
}
|
||||
if (!reader.IsDBNull(61))
|
||||
{
|
||||
episode.SeasonId = reader.GetGuid(61);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSeries != null)
|
||||
{
|
||||
if (!reader.IsDBNull(62))
|
||||
{
|
||||
hasSeries.SeriesId = reader.GetGuid(62);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSeries != null)
|
||||
{
|
||||
if (!reader.IsDBNull(63))
|
||||
{
|
||||
hasSeries.SeriesSortName = reader.GetString(63);
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
|
@ -1437,7 +1514,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
|
||||
using (var cmd = _connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
|
||||
cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
|
||||
|
||||
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
|
||||
|
||||
|
@ -1470,7 +1547,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
|
||||
using (var cmd = _connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
|
||||
cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
|
||||
|
||||
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
|
||||
cmd.Parameters.Add(cmd, "@ChapterIndex", DbType.Int32).Value = index;
|
||||
|
@ -1508,6 +1585,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
chapter.ImagePath = reader.GetString(2);
|
||||
}
|
||||
|
||||
if (!reader.IsDBNull(3))
|
||||
{
|
||||
chapter.ImageDateModified = reader.GetDateTime(3).ToUniversalTime();
|
||||
}
|
||||
|
||||
return chapter;
|
||||
}
|
||||
|
||||
|
@ -1567,6 +1649,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
_saveChapterCommand.GetParameter(2).Value = chapter.StartPositionTicks;
|
||||
_saveChapterCommand.GetParameter(3).Value = chapter.Name;
|
||||
_saveChapterCommand.GetParameter(4).Value = chapter.ImagePath;
|
||||
_saveChapterCommand.GetParameter(5).Value = chapter.ImageDateModified;
|
||||
|
||||
_saveChapterCommand.Transaction = transaction;
|
||||
|
||||
|
@ -2061,7 +2144,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
query.SortBy = new[] { "SimilarityScore", ItemSortBy.IsPlayed, ItemSortBy.Random };
|
||||
query.SortBy = new[] { ItemSortBy.IsPlayed, "SimilarityScore", ItemSortBy.Random };
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2986,6 +3069,39 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
whereClauses.Add("LocationType<>'Virtual'");
|
||||
}
|
||||
}
|
||||
if (query.IsUnaired.HasValue)
|
||||
{
|
||||
if (query.IsUnaired.Value)
|
||||
{
|
||||
whereClauses.Add("PremiereDate >= DATETIME('now')");
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("PremiereDate < DATETIME('now')");
|
||||
}
|
||||
}
|
||||
if (query.IsMissing.HasValue && _config.Configuration.SchemaVersion >= 90)
|
||||
{
|
||||
if (query.IsMissing.Value)
|
||||
{
|
||||
whereClauses.Add("(IsVirtualItem=1 AND PremiereDate < DATETIME('now'))");
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("(IsVirtualItem=0 OR PremiereDate >= DATETIME('now'))");
|
||||
}
|
||||
}
|
||||
if (query.IsVirtualUnaired.HasValue && _config.Configuration.SchemaVersion >= 90)
|
||||
{
|
||||
if (query.IsVirtualUnaired.Value)
|
||||
{
|
||||
whereClauses.Add("(IsVirtualItem=1 AND PremiereDate >= DATETIME('now'))");
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))");
|
||||
}
|
||||
}
|
||||
if (query.MediaTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("MediaType=@MediaTypes");
|
||||
|
@ -4063,6 +4179,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
throw new ArgumentNullException("values");
|
||||
}
|
||||
|
||||
// Just in case there might be case-insensitive duplicates, strip them out now
|
||||
var newValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var pair in values)
|
||||
{
|
||||
newValues[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
// First delete
|
||||
|
@ -4071,7 +4194,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
|
||||
_deleteProviderIdsCommand.ExecuteNonQuery();
|
||||
|
||||
foreach (var pair in values)
|
||||
foreach (var pair in newValues)
|
||||
{
|
||||
_saveProviderIdsCommand.GetParameter(0).Value = itemId;
|
||||
_saveProviderIdsCommand.GetParameter(1).Value = pair.Key;
|
||||
|
|
|
@ -247,15 +247,18 @@ namespace MediaBrowser.Server.Implementations.Playlists
|
|||
return;
|
||||
}
|
||||
|
||||
if (newIndex > oldIndex)
|
||||
{
|
||||
newIndex--;
|
||||
}
|
||||
|
||||
var item = playlist.LinkedChildren[oldIndex];
|
||||
|
||||
playlist.LinkedChildren.Remove(item);
|
||||
playlist.LinkedChildren.Insert(newIndex, item);
|
||||
|
||||
if (newIndex >= playlist.LinkedChildren.Count)
|
||||
{
|
||||
playlist.LinkedChildren.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
playlist.LinkedChildren.Insert(newIndex, item);
|
||||
}
|
||||
|
||||
await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using System;
|
||||
|
@ -21,28 +20,9 @@ namespace MediaBrowser.Server.Implementations.Sorting
|
|||
|
||||
private string GetValue(BaseItem item)
|
||||
{
|
||||
Series series = null;
|
||||
var hasSeries = item as IHasSeries;
|
||||
|
||||
var season = item as Season;
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
series = season.Series;
|
||||
}
|
||||
|
||||
var episode = item as Episode;
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
series = episode.Series;
|
||||
}
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
series = item as Series;
|
||||
}
|
||||
|
||||
return series != null ? series.SortName : null;
|
||||
return hasSeries != null ? hasSeries.SeriesSortName : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -50,6 +50,11 @@ namespace MediaBrowser.Server.Implementations.TV
|
|||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
|
||||
{
|
||||
limit = limit.Value + 10;
|
||||
}
|
||||
|
||||
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
|
@ -89,6 +94,11 @@ namespace MediaBrowser.Server.Implementations.TV
|
|||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
|
||||
{
|
||||
limit = limit.Value + 10;
|
||||
}
|
||||
|
||||
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
|
@ -115,7 +125,8 @@ namespace MediaBrowser.Server.Implementations.TV
|
|||
.Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
|
||||
.OrderByDescending(i => i.Item2)
|
||||
.ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
|
||||
.Select(i => i.Item1);
|
||||
.Select(i => i.Item1)
|
||||
.Take(request.Limit ?? int.MaxValue);
|
||||
}
|
||||
|
||||
private string GetUniqueSeriesKey(BaseItem series)
|
||||
|
@ -143,7 +154,6 @@ namespace MediaBrowser.Server.Implementations.TV
|
|||
SortOrder = SortOrder.Descending,
|
||||
IsPlayed = true,
|
||||
Limit = 1,
|
||||
IsVirtualItem = false,
|
||||
ParentIndexNumberNotEquals = 0
|
||||
|
||||
}).FirstOrDefault();
|
||||
|
|
|
@ -54,11 +54,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
|
|||
{
|
||||
return series;
|
||||
}
|
||||
var episodeSeason = episode.Season;
|
||||
if (episodeSeason != null)
|
||||
{
|
||||
return episodeSeason;
|
||||
}
|
||||
|
||||
return episode;
|
||||
}
|
||||
|
|
|
@ -86,11 +86,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
|
|||
{
|
||||
return series;
|
||||
}
|
||||
var episodeSeason = episode.Season;
|
||||
if (episodeSeason != null)
|
||||
{
|
||||
return episodeSeason;
|
||||
}
|
||||
|
||||
return episode;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
<package id="Emby.XmlTv" version="1.0.0.55" targetFramework="net45" />
|
||||
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
|
||||
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
|
||||
<package id="MediaBrowser.Naming" version="1.0.0.52" targetFramework="net45" />
|
||||
<package id="MediaBrowser.Naming" version="1.0.0.53" targetFramework="net45" />
|
||||
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
|
||||
<package id="morelinq" version="1.4.0" targetFramework="net45" />
|
||||
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
||||
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
|
||||
<package id="SocketHttpListener" version="1.0.0.30" targetFramework="net45" />
|
||||
<package id="SocketHttpListener" version="1.0.0.35" targetFramework="net45" />
|
||||
</packages>
|
|
@ -274,7 +274,7 @@ namespace MediaBrowser.Server.Startup.Common
|
|||
{
|
||||
get
|
||||
{
|
||||
return "Media Browser Server";
|
||||
return "Emby Server";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ using System.Configuration.Install;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
|
@ -102,7 +103,7 @@ namespace MediaBrowser.ServerApplication
|
|||
|
||||
if (IsAlreadyRunning(applicationPath, currentProcess))
|
||||
{
|
||||
logger.Info("Shutting down because another instance of Media Browser Server is already running.");
|
||||
logger.Info("Shutting down because another instance of Emby Server is already running.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -130,13 +131,28 @@ namespace MediaBrowser.ServerApplication
|
|||
/// <returns><c>true</c> if [is already running] [the specified current process]; otherwise, <c>false</c>.</returns>
|
||||
private static bool IsAlreadyRunning(string applicationPath, Process currentProcess)
|
||||
{
|
||||
var filename = Path.GetFileName(applicationPath);
|
||||
|
||||
var duplicate = Process.GetProcesses().FirstOrDefault(i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return string.Equals(filename, Path.GetFileName(i.MainModule.FileName)) && currentProcess.Id != i.Id;
|
||||
if (currentProcess.Id == i.Id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//_logger.Info("Module: {0}", i.MainModule.FileName);
|
||||
if (string.Equals(applicationPath, i.MainModule.FileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -155,6 +171,41 @@ namespace MediaBrowser.ServerApplication
|
|||
}
|
||||
}
|
||||
|
||||
if (!_isRunningAsService)
|
||||
{
|
||||
return IsAlreadyRunningAsService(applicationPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsAlreadyRunningAsService(string applicationPath)
|
||||
{
|
||||
var serviceName = BackgroundService.GetExistingServiceName();
|
||||
|
||||
WqlObjectQuery wqlObjectQuery = new WqlObjectQuery(string.Format("SELECT * FROM Win32_Service WHERE State = 'Running' AND Name = '{0}'", serviceName));
|
||||
ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(wqlObjectQuery);
|
||||
ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
|
||||
|
||||
foreach (ManagementObject managementObject in managementObjectCollection)
|
||||
{
|
||||
var obj = managementObject.GetPropertyValue("PathName");
|
||||
if (obj == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var path = obj.ToString();
|
||||
|
||||
_logger.Info("Service path: {0}", path);
|
||||
// Need to use indexOf instead of equality because the path will have the full service command line
|
||||
if (path.IndexOf(applicationPath, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
_logger.Info("The windows service is already running");
|
||||
MessageBox.Show("Emby Server is already running as a Windows Service. Only one instance is allowed at a time. To run as a tray icon, shut down the Windows Service.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -593,14 +644,32 @@ namespace MediaBrowser.ServerApplication
|
|||
|
||||
private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
|
||||
{
|
||||
// Reference
|
||||
// http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed
|
||||
|
||||
try
|
||||
{
|
||||
var version = ImageMagickEncoder.GetVersion();
|
||||
return;
|
||||
var subkey = Environment.Is64BitProcess
|
||||
? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x64"
|
||||
: "SOFTWARE\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x86";
|
||||
|
||||
using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
|
||||
.OpenSubKey(subkey))
|
||||
{
|
||||
if (ndpKey != null && ndpKey.GetValue("Version") != null)
|
||||
{
|
||||
var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v');
|
||||
if (installedVersion.StartsWith("12", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.ErrorException("Error loading ImageMagick", ex);
|
||||
logger.ErrorException("Error getting .NET Framework version", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
|
|
|
@ -349,21 +349,16 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
}
|
||||
|
||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
|
||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
|
||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
|
||||
//_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
|
||||
//_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
|
||||
|
||||
DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
|
||||
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "jquery"), "src");
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
|
||||
|
||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true);
|
||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true);
|
||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism"), true);
|
||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism-element"), true);
|
||||
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
|
||||
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
|
||||
//DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
|
||||
|
||||
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
|
@ -104,6 +104,9 @@
|
|||
<Content Include="dashboard-ui\components\apphost.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\components\categorysyncbuttons.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\components\channelmapper\channelmapper.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@ -902,9 +905,6 @@
|
|||
<Content Include="dashboard-ui\scripts\favorites.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\scripts\librarylist.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\scripts\librarymenu.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@ -1043,42 +1043,6 @@
|
|||
<Content Include="dashboard-ui\userpassword.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\commands\controlcommands.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\commands\disablecommands.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\commands\enablecommands.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\commands\playcommands.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\commands\searchcommands.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\commands\showcommands.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\commands\togglecommands.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\grammarprocessor.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\voicedialog.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\voice.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\voice.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\voice\voicecommands.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\wizardagreement.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@ -1674,15 +1638,6 @@
|
|||
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Include="dashboard-ui\voice\grammar\en-US.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="dashboard-ui\voice\grammar\grammar.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="dashboard-ui\voice\Readme.md">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
|
|
Loading…
Reference in New Issue