Merge pull request #1222 from MediaBrowser/dev

3.0.5768.7
This commit is contained in:
Luke 2015-10-26 18:50:19 -04:00
commit 35778ebc02
512 changed files with 7399 additions and 6520 deletions

View file

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using CommonIO;
namespace Emby.Drawing.Common
{
@ -220,4 +221,4 @@ namespace Emby.Drawing.Common
throw new ArgumentException(ErrorMessage);
}
}
}
}

View file

@ -12,7 +12,6 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -32,10 +31,20 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="CommonIO, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\CommonIO.1.0.0.5\lib\net45\CommonIO.dll</HintPath>
</Reference>
<Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ImageMagickSharp.1.0.0.16\lib\net45\ImageMagickSharp.dll</HintPath>
</Reference>
<Reference Include="Patterns.Logging">
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
</Reference>
<Reference Include="policy.2.0.taglib-sharp">
<HintPath>..\packages\taglib.2.1.0.0\lib\policy.2.0.taglib-sharp.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
@ -44,11 +53,15 @@
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="taglib-sharp">
<HintPath>..\packages\taglib.2.1.0.0\lib\taglib-sharp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="Common\ImageHeader.cs" />
<Compile Include="GDI\DynamicImageHelpers.cs" />
<Compile Include="GDI\GDIImageEncoder.cs" />
<Compile Include="GDI\ImageExtensions.cs" />
@ -56,13 +69,13 @@
<Compile Include="GDI\PlayedIndicatorDrawer.cs" />
<Compile Include="GDI\UnplayedCountIndicator.cs" />
<Compile Include="IImageEncoder.cs" />
<Compile Include="Common\ImageHeader.cs" />
<Compile Include="ImageHelpers.cs" />
<Compile Include="ImageMagick\ImageMagickEncoder.cs" />
<Compile Include="ImageMagick\StripCollageBuilder.cs" />
<Compile Include="ImageProcessor.cs" />
<Compile Include="ImageMagick\PercentPlayedDrawer.cs" />
<Compile Include="ImageMagick\PlayedIndicatorDrawer.cs" />
<Compile Include="NullImageEncoder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ImageMagick\UnplayedCountIndicator.cs" />
</ItemGroup>
@ -87,6 +100,9 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="GDI\empty.png" />
</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.

View file

@ -4,6 +4,7 @@ using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using CommonIO;
namespace Emby.Drawing.GDI
{

View file

@ -1,5 +1,4 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Logging;
using System;
@ -8,6 +7,7 @@ using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using CommonIO;
using ImageFormat = MediaBrowser.Model.Drawing.ImageFormat;
namespace Emby.Drawing.GDI
@ -22,7 +22,20 @@ namespace Emby.Drawing.GDI
_fileSystem = fileSystem;
_logger = logger;
_logger.Info("GDI image processor initialized");
LogInfo();
}
private void LogInfo()
{
_logger.Info("GDIImageEncoder starting");
using (var stream = GetType().Assembly.GetManifestResourceStream(GetType().Namespace + ".empty.png"))
{
using (var img = Image.FromStream(stream))
{
}
}
_logger.Info("GDIImageEncoder started");
}
public string[] SupportedInputFormats
@ -66,7 +79,7 @@ namespace Emby.Drawing.GDI
{
using (var croppedImage = image.CropWhitespace())
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
using (var outputStream = _fileSystem.GetFileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read, false))
{
@ -120,7 +133,7 @@ namespace Emby.Drawing.GDI
var outputFormat = GetOutputFormat(originalImage, selectedOutputFormat);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
// Save to the cache location
using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, false))
@ -252,5 +265,15 @@ namespace Emby.Drawing.GDI
{
get { return "GDI"; }
}
public bool SupportsImageCollageCreation
{
get { return true; }
}
public bool SupportsImageEncoding
{
get { return true; }
}
}
}

BIN
Emby.Drawing/GDI/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

View file

@ -17,12 +17,6 @@ namespace Emby.Drawing
/// <value>The supported output formats.</value>
ImageFormat[] SupportedOutputFormats { get; }
/// <summary>
/// Gets the size of the image.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>ImageSize.</returns>
ImageSize GetImageSize(string path);
/// <summary>
/// Crops the white space.
/// </summary>
/// <param name="inputPath">The input path.</param>
@ -49,5 +43,17 @@ namespace Emby.Drawing
/// </summary>
/// <value>The name.</value>
string Name { get; }
/// <summary>
/// Gets a value indicating whether [supports image collage creation].
/// </summary>
/// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value>
bool SupportsImageCollageCreation { get; }
/// <summary>
/// Gets a value indicating whether [supports image encoding].
/// </summary>
/// <value><c>true</c> if [supports image encoding]; otherwise, <c>false</c>.</value>
bool SupportsImageEncoding { get; }
}
}

View file

@ -8,6 +8,7 @@ using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Linq;
using CommonIO;
namespace Emby.Drawing.ImageMagick
{
@ -16,14 +17,16 @@ namespace Emby.Drawing.ImageMagick
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
public ImageMagickEncoder(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient)
public ImageMagickEncoder(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IFileSystem fileSystem)
{
_logger = logger;
_appPaths = appPaths;
_httpClient = httpClient;
_fileSystem = fileSystem;
LogImageMagickVersion();
LogVersion();
}
public string[] SupportedInputFormats
@ -64,7 +67,7 @@ namespace Emby.Drawing.ImageMagick
}
}
private void LogImageMagickVersion()
private void LogVersion()
{
_logger.Info("ImageMagick version: " + Wand.VersionString);
TestWebp();
@ -77,16 +80,16 @@ namespace Emby.Drawing.ImageMagick
try
{
var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".webp");
Directory.CreateDirectory(Path.GetDirectoryName(tmpPath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
using (var wand = new MagickWand(1, 1, new PixelWand("none", 1)))
{
wand.SaveImage(tmpPath);
}
}
catch (Exception ex)
catch
{
_logger.ErrorException("Error loading webp: ", ex);
//_logger.ErrorException("Error loading webp: ", ex);
_webpAvailable = false;
}
}
@ -100,6 +103,7 @@ namespace Emby.Drawing.ImageMagick
wand.CurrentImage.TrimImage(10);
wand.SaveImage(outputPath);
}
SaveDelay();
}
public ImageSize GetImageSize(string path)
@ -159,6 +163,7 @@ namespace Emby.Drawing.ImageMagick
}
}
}
SaveDelay();
}
/// <summary>
@ -181,14 +186,14 @@ namespace Emby.Drawing.ImageMagick
{
var currentImageSize = new ImageSize(imageWidth, imageHeight);
var task = new PlayedIndicatorDrawer(_appPaths, _httpClient).DrawPlayedIndicator(wand, currentImageSize);
var task = new PlayedIndicatorDrawer(_appPaths, _httpClient, _fileSystem).DrawPlayedIndicator(wand, currentImageSize);
Task.WaitAll(task);
}
else if (options.UnplayedCount.HasValue)
{
var currentImageSize = new ImageSize(imageWidth, imageHeight);
new UnplayedCountIndicator(_appPaths).DrawUnplayedCountIndicator(wand, currentImageSize, options.UnplayedCount.Value);
new UnplayedCountIndicator(_appPaths, _fileSystem).DrawUnplayedCountIndicator(wand, currentImageSize, options.UnplayedCount.Value);
}
if (options.PercentPlayed > 0)
@ -209,16 +214,25 @@ namespace Emby.Drawing.ImageMagick
if (ratio >= 1.4)
{
new StripCollageBuilder(_appPaths).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
}
else if (ratio >= .9)
{
new StripCollageBuilder(_appPaths).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
}
else
{
new StripCollageBuilder(_appPaths).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
}
SaveDelay();
}
private void SaveDelay()
{
// For some reason the images are not always getting released right away
var task = Task.Delay(300);
Task.WaitAll(task);
}
public string Name
@ -240,5 +254,15 @@ namespace Emby.Drawing.ImageMagick
throw new ObjectDisposedException(GetType().Name);
}
}
public bool SupportsImageCollageCreation
{
get { return true; }
}
public bool SupportsImageEncoding
{
get { return true; }
}
}
}

View file

@ -5,6 +5,8 @@ using MediaBrowser.Model.Drawing;
using System;
using System.IO;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
namespace Emby.Drawing.ImageMagick
{
@ -14,12 +16,14 @@ namespace Emby.Drawing.ImageMagick
private const int OffsetFromTopRightCorner = 38;
private readonly IApplicationPaths _appPaths;
private readonly IHttpClient _iHttpClient;
private readonly IHttpClient _iHttpClient;
private readonly IFileSystem _fileSystem;
public PlayedIndicatorDrawer(IApplicationPaths appPaths, IHttpClient iHttpClient)
public PlayedIndicatorDrawer(IApplicationPaths appPaths, IHttpClient iHttpClient, IFileSystem fileSystem)
{
_appPaths = appPaths;
_iHttpClient = iHttpClient;
_fileSystem = fileSystem;
}
public async Task DrawPlayedIndicator(MagickWand wand, ImageSize imageSize)
@ -38,7 +42,7 @@ namespace Emby.Drawing.ImageMagick
pixel.Opacity = 0;
pixel.Color = "white";
draw.FillColor = pixel;
draw.Font = await DownloadFont("webdings.ttf", "https://github.com/MediaBrowser/Emby.Resources/raw/master/fonts/webdings.ttf", _appPaths, _iHttpClient).ConfigureAwait(false);
draw.Font = await DownloadFont("webdings.ttf", "https://github.com/MediaBrowser/Emby.Resources/raw/master/fonts/webdings.ttf", _appPaths, _iHttpClient, _fileSystem).ConfigureAwait(false);
draw.FontSize = FontSize;
draw.FontStyle = FontStyleType.NormalStyle;
draw.TextAlignment = TextAlignType.CenterAlign;
@ -52,18 +56,18 @@ namespace Emby.Drawing.ImageMagick
}
}
internal static string ExtractFont(string name, IApplicationPaths paths)
internal static string ExtractFont(string name, IApplicationPaths paths, IFileSystem fileSystem)
{
var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name);
if (File.Exists(filePath))
if (fileSystem.FileExists(filePath))
{
return filePath;
}
var namespacePath = typeof(PlayedIndicatorDrawer).Namespace + ".fonts." + name;
var tempPath = Path.Combine(paths.TempDirectory, Guid.NewGuid().ToString("N") + ".ttf");
Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
fileSystem.CreateDirectory(Path.GetDirectoryName(tempPath));
using (var stream = typeof(PlayedIndicatorDrawer).Assembly.GetManifestResourceStream(namespacePath))
{
@ -73,11 +77,11 @@ namespace Emby.Drawing.ImageMagick
}
}
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
fileSystem.CreateDirectory(Path.GetDirectoryName(filePath));
try
{
File.Copy(tempPath, filePath, false);
fileSystem.CopyFile(tempPath, filePath, false);
}
catch (IOException)
{
@ -87,11 +91,11 @@ namespace Emby.Drawing.ImageMagick
return tempPath;
}
internal static async Task<string> DownloadFont(string name, string url, IApplicationPaths paths, IHttpClient httpClient)
internal static async Task<string> DownloadFont(string name, string url, IApplicationPaths paths, IHttpClient httpClient, IFileSystem fileSystem)
{
var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name);
if (File.Exists(filePath))
if (fileSystem.FileExists(filePath))
{
return filePath;
}
@ -103,11 +107,11 @@ namespace Emby.Drawing.ImageMagick
}).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
fileSystem.CreateDirectory(Path.GetDirectoryName(filePath));
try
{
File.Copy(tempPath, filePath, false);
fileSystem.CopyFile(tempPath, filePath, false);
}
catch (IOException)
{

View file

@ -2,16 +2,20 @@
using MediaBrowser.Common.Configuration;
using System;
using System.Collections.Generic;
using CommonIO;
using MediaBrowser.Common.IO;
namespace Emby.Drawing.ImageMagick
{
public class StripCollageBuilder
{
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
public StripCollageBuilder(IApplicationPaths appPaths)
public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem)
{
_appPaths = appPaths;
_fileSystem = fileSystem;
}
public void BuildPosterCollage(List<string> paths, string outputPath, int width, int height, string text)
@ -145,17 +149,17 @@ namespace Emby.Drawing.ImageMagick
private MagickWand BuildPosterCollageWand(List<string> paths, int width, int height)
{
var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
var inputPaths = ImageHelpers.ProjectPaths(paths, 3);
using (var wandImages = new MagickWand(inputPaths.ToArray()))
{
var wand = new MagickWand(width, height);
wand.OpenImage("gradient:#111111-#111111");
using (var draw = new DrawingWand())
{
var iSlice = Convert.ToInt32(width * 0.225);
var iSlice = Convert.ToInt32(width * 0.3);
int iTrans = Convert.ToInt32(height * .25);
int iHeight = Convert.ToInt32(height * .65);
var horizontalImagePadding = Convert.ToInt32(width * 0.0275);
var horizontalImagePadding = Convert.ToInt32(width * 0.0366);
foreach (var element in wandImages.ImageList)
{
@ -350,14 +354,14 @@ namespace Emby.Drawing.ImageMagick
private MagickWand BuildSquareCollageWand(List<string> paths, int width, int height)
{
var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
var inputPaths = ImageHelpers.ProjectPaths(paths, 3);
using (var wandImages = new MagickWand(inputPaths.ToArray()))
{
var wand = new MagickWand(width, height);
wand.OpenImage("gradient:#111111-#111111");
using (var draw = new DrawingWand())
{
var iSlice = Convert.ToInt32(width * .225);
var iSlice = Convert.ToInt32(width * .3);
int iTrans = Convert.ToInt32(height * .25);
int iHeight = Convert.ToInt32(height * .63);
var horizontalImagePadding = Convert.ToInt32(width * 0.02);
@ -490,7 +494,7 @@ namespace Emby.Drawing.ImageMagick
private string MontserratLightFont
{
get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths); }
get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths, _fileSystem); }
}
}
}

View file

@ -1,7 +1,9 @@
using ImageMagickSharp;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.Drawing;
using System.Globalization;
using CommonIO;
namespace Emby.Drawing.ImageMagick
{
@ -10,10 +12,12 @@ namespace Emby.Drawing.ImageMagick
private const int OffsetFromTopRightCorner = 38;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
public UnplayedCountIndicator(IApplicationPaths appPaths)
public UnplayedCountIndicator(IApplicationPaths appPaths, IFileSystem fileSystem)
{
_appPaths = appPaths;
_fileSystem = fileSystem;
}
public void DrawUnplayedCountIndicator(MagickWand wand, ImageSize imageSize, int count)
@ -33,7 +37,7 @@ namespace Emby.Drawing.ImageMagick
pixel.Opacity = 0;
pixel.Color = "white";
draw.FillColor = pixel;
draw.Font = PlayedIndicatorDrawer.ExtractFont("robotoregular.ttf", _appPaths);
draw.Font = PlayedIndicatorDrawer.ExtractFont("robotoregular.ttf", _appPaths, _fileSystem);
draw.FontStyle = FontStyleType.NormalStyle;
draw.TextAlignment = TextAlignType.CenterAlign;
draw.FontWeight = FontWeightType.RegularStyle;

View file

@ -1,5 +1,4 @@
using Emby.Drawing.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing;
@ -17,6 +16,9 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using Emby.Drawing.Common;
using MediaBrowser.Controller.Library;
namespace Emby.Drawing
{
@ -52,18 +54,20 @@ namespace Emby.Drawing
private readonly IServerApplicationPaths _appPaths;
private readonly IImageEncoder _imageEncoder;
private readonly SemaphoreSlim _imageProcessingSemaphore;
private readonly Func<ILibraryManager> _libraryManager;
public ImageProcessor(ILogger logger,
IServerApplicationPaths appPaths,
IFileSystem fileSystem,
IJsonSerializer jsonSerializer,
IImageEncoder imageEncoder,
int maxConcurrentImageProcesses)
int maxConcurrentImageProcesses, Func<ILibraryManager> libraryManager)
{
_logger = logger;
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
_imageEncoder = imageEncoder;
_libraryManager = libraryManager;
_appPaths = appPaths;
ImageEnhancers = new List<IImageEnhancer>();
@ -106,6 +110,15 @@ namespace Emby.Drawing
}
}
public bool SupportsImageCollageCreation
{
get
{
return _imageEncoder.SupportsImageCollageCreation;
}
}
private string ResizedImageCachePath
{
get
@ -157,7 +170,19 @@ namespace Emby.Drawing
throw new ArgumentNullException("options");
}
var originalImagePath = options.Image.Path;
var originalImage = options.Image;
if (!originalImage.IsLocalFile)
{
originalImage = await _libraryManager().ConvertImageToLocal(options.Item, originalImage, options.ImageIndex).ConfigureAwait(false);
}
var originalImagePath = originalImage.Path;
if (!_imageEncoder.SupportsImageEncoding)
{
return originalImagePath;
}
if (options.HasDefaultOptions(originalImagePath) && options.Enhancers.Count == 0 && !options.CropWhiteSpace)
{
@ -165,9 +190,9 @@ namespace Emby.Drawing
return originalImagePath;
}
var dateModified = options.Image.DateModified;
var dateModified = originalImage.DateModified;
if (options.CropWhiteSpace)
if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
{
var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
@ -180,7 +205,7 @@ namespace Emby.Drawing
var tuple = await GetEnhancedImage(new ItemImageInfo
{
DateModified = dateModified,
Type = options.Image.Type,
Type = originalImage.Type,
Path = originalImagePath
}, options.Item, options.ImageIndex, options.Enhancers).ConfigureAwait(false);
@ -215,21 +240,18 @@ namespace Emby.Drawing
{
CheckDisposed();
if (!File.Exists(cacheFilePath))
if (!_fileSystem.FileExists(cacheFilePath))
{
var newWidth = Convert.ToInt32(newSize.Width);
var newHeight = Convert.ToInt32(newSize.Height);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
imageProcessingLockTaken = true;
_imageEncoder.EncodeImage(originalImagePath, cacheFilePath, newWidth, newHeight, quality, options);
// ImageMagick doesn't seem to always release it right away
await Task.Delay(100).ConfigureAwait(false);
}
}
finally
@ -270,7 +292,7 @@ namespace Emby.Drawing
await semaphore.WaitAsync().ConfigureAwait(false);
// Check again in case of contention
if (File.Exists(croppedImagePath))
if (_fileSystem.FileExists(croppedImagePath))
{
semaphore.Release();
return GetResult(croppedImagePath);
@ -280,13 +302,18 @@ namespace Emby.Drawing
try
{
Directory.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
imageProcessingLockTaken = true;
_imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath);
}
catch (NotImplementedException)
{
// No need to spam the log with an error message
return new Tuple<string, DateTime>(originalImagePath, dateModified);
}
catch (Exception ex)
{
// We have to have a catch-all here because some of the .net image methods throw a plain old Exception
@ -359,21 +386,16 @@ namespace Emby.Drawing
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower());
}
/// <summary>
/// Gets the size of the image.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>ImageSize.</returns>
public ImageSize GetImageSize(string path)
{
return GetImageSize(path, File.GetLastWriteTimeUtc(path), false);
}
public ImageSize GetImageSize(ItemImageInfo info)
{
return GetImageSize(info.Path, info.DateModified, false);
}
public ImageSize GetImageSize(string path)
{
return GetImageSize(path, _fileSystem.GetLastWriteTimeUtc(path), false);
}
/// <summary>
/// Gets the size of the image.
/// </summary>
@ -399,7 +421,11 @@ namespace Emby.Drawing
{
size = GetImageSizeInternal(path, allowSlowMethod);
_cachedImagedSizes.AddOrUpdate(cacheHash, size, (keyName, oldValue) => size);
if (size.Width > 0 && size.Height > 0)
{
StartSaveImageSizeTimer();
_cachedImagedSizes.AddOrUpdate(cacheHash, size, (keyName, oldValue) => size);
}
}
return size;
@ -413,28 +439,26 @@ namespace Emby.Drawing
/// <returns>ImageSize.</returns>
private ImageSize GetImageSizeInternal(string path, bool allowSlowMethod)
{
ImageSize size;
try
{
size = ImageHeader.GetDimensions(path, _logger, _fileSystem);
using (var file = TagLib.File.Create(path))
{
var image = file as TagLib.Image.File;
var properties = image.Properties;
return new ImageSize
{
Height = properties.PhotoHeight,
Width = properties.PhotoWidth
};
}
}
catch
{
if (!allowSlowMethod)
{
throw;
}
//_logger.Info("Failed to read image header for {0}. Doing it the slow way.", path);
CheckDisposed();
size = _imageEncoder.GetImageSize(path);
}
StartSaveImageSizeTimer();
return size;
return ImageHeader.GetDimensions(path, _logger, _fileSystem);
}
private readonly Timer _saveImageSizeTimer;
@ -452,7 +476,7 @@ namespace Emby.Drawing
try
{
var path = ImageSizeFile;
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
_jsonSerializer.SerializeToFile(_cachedImagedSizes, path);
}
catch (Exception ex)
@ -624,7 +648,7 @@ namespace Emby.Drawing
await semaphore.WaitAsync().ConfigureAwait(false);
// Check again in case of contention
if (File.Exists(enhancedImagePath))
if (_fileSystem.FileExists(enhancedImagePath))
{
semaphore.Release();
return enhancedImagePath;
@ -634,7 +658,7 @@ namespace Emby.Drawing
try
{
Directory.CreateDirectory(Path.GetDirectoryName(enhancedImagePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(enhancedImagePath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
@ -773,11 +797,11 @@ namespace Emby.Drawing
try
{
_logger.Debug("Creating image collage and saving to {0}", options.OutputPath);
_logger.Info("Creating image collage and saving to {0}", options.OutputPath);
_imageEncoder.CreateImageCollage(options);
_logger.Debug("Completed creation of image collage and saved to {0}", options.OutputPath);
_logger.Info("Completed creation of image collage and saved to {0}", options.OutputPath);
}
finally
{
@ -799,7 +823,6 @@ namespace Emby.Drawing
return false;
}
});
}
@ -819,4 +842,4 @@ namespace Emby.Drawing
}
}
}
}
}

View file

@ -0,0 +1,64 @@
using System;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Drawing;
namespace Emby.Drawing
{
public class NullImageEncoder : IImageEncoder
{
public string[] SupportedInputFormats
{
get
{
return new[]
{
"png",
"jpeg",
"jpg"
};
}
}
public ImageFormat[] SupportedOutputFormats
{
get
{
return new[] { ImageFormat.Jpg, ImageFormat.Png };
}
}
public void CropWhiteSpace(string inputPath, string outputPath)
{
throw new NotImplementedException();
}
public void EncodeImage(string inputPath, string outputPath, int width, int height, int quality, ImageProcessingOptions options)
{
throw new NotImplementedException();
}
public void CreateImageCollage(ImageCollageOptions options)
{
throw new NotImplementedException();
}
public string Name
{
get { return "Null Image Encoder"; }
}
public bool SupportsImageCollageCreation
{
get { return false; }
}
public bool SupportsImageEncoding
{
get { return false; }
}
public void Dispose()
{
}
}
}

View file

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonIO" version="1.0.0.5" targetFramework="net45" />
<package id="ImageMagickSharp" version="1.0.0.16" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
</packages>

View file

@ -15,6 +15,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api
{
@ -95,7 +96,7 @@ namespace MediaBrowser.Api
{
var path = _config.ApplicationPaths.TranscodingTempPath;
foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
foreach (var file in _fileSystem.GetFilePaths(path, true)
.ToList())
{
_fileSystem.DeleteFile(file);
@ -567,7 +568,7 @@ namespace MediaBrowser.Api
var directory = Path.GetDirectoryName(outputFilePath);
var name = Path.GetFileNameWithoutExtension(outputFilePath);
var filesToDelete = Directory.EnumerateFiles(directory)
var filesToDelete = _fileSystem.GetFilePaths(directory)
.Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1)
.ToList();
@ -577,7 +578,7 @@ namespace MediaBrowser.Api
{
try
{
Logger.Info("Deleting HLS file {0}", file);
Logger.Debug("Deleting HLS file {0}", file);
_fileSystem.DeleteFile(file);
}
catch (DirectoryNotFoundException)

View file

@ -1,100 +0,0 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Themes;
using MediaBrowser.Model.Themes;
using ServiceStack;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace MediaBrowser.Api
{
[Route("/Themes", "GET", Summary = "Gets a list of available themes for an app")]
public class GetAppThemes : IReturn<List<AppThemeInfo>>
{
[ApiMember(Name = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string App { get; set; }
}
[Route("/Themes/Info", "GET", Summary = "Gets an app theme")]
public class GetAppTheme : IReturn<AppTheme>
{
[ApiMember(Name = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string App { get; set; }
[ApiMember(Name = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Name { get; set; }
}
[Route("/Themes/Images", "GET", Summary = "Gets an app theme")]
public class GetAppThemeImage
{
[ApiMember(Name = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string App { get; set; }
[ApiMember(Name = "Theme", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Theme { get; set; }
[ApiMember(Name = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Name { get; set; }
[ApiMember(Name = "CacheTag", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string CacheTag { get; set; }
}
[Route("/Themes", "POST", Summary = "Saves a theme")]
public class SaveTheme : AppTheme, IReturnVoid
{
}
[Authenticated]
public class AppThemeService : BaseApiService
{
private readonly IAppThemeManager _themeManager;
private readonly IFileSystem _fileSystem;
public AppThemeService(IAppThemeManager themeManager, IFileSystem fileSystem)
{
_themeManager = themeManager;
_fileSystem = fileSystem;
}
public object Get(GetAppThemes request)
{
var result = _themeManager.GetThemes(request.App).ToList();
return ToOptimizedResult(result);
}
public object Get(GetAppTheme request)
{
var result = _themeManager.GetTheme(request.App, request.Name);
return ToOptimizedResult(result);
}
public void Post(SaveTheme request)
{
_themeManager.SaveTheme(request);
}
public object Get(GetAppThemeImage request)
{
var info = _themeManager.GetImageImageInfo(request.App, request.Theme, request.Name);
var cacheGuid = new Guid(info.CacheTag);
TimeSpan? cacheDuration = null;
if (!string.IsNullOrEmpty(request.CacheTag) && cacheGuid == new Guid(request.CacheTag))
{
cacheDuration = TimeSpan.FromDays(365);
}
var contentType = MimeTypes.GetMimeType(info.Path);
return ResultFactory.GetCachedResult(Request, cacheGuid, null, cacheDuration, () => _fileSystem.GetFileStream(info.Path, FileMode.Open, FileAccess.Read, FileShare.Read), contentType);
}
}
}

View file

@ -11,6 +11,7 @@ using ServiceStack.Web;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using CommonIO;
namespace MediaBrowser.Api
{

View file

@ -108,7 +108,6 @@ namespace MediaBrowser.Api.Dlna
private readonly IConnectionManager _connectionManager;
private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar;
// TODO: Add utf-8
private const string XMLContentType = "text/xml; charset=UTF-8";
public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IMediaReceiverRegistrar mediaReceiverRegistrar)

View file

@ -1,4 +1,5 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
@ -8,6 +9,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using CommonIO;
namespace MediaBrowser.Api
{
@ -96,13 +98,14 @@ namespace MediaBrowser.Api
/// The _network manager
/// </summary>
private readonly INetworkManager _networkManager;
private IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="EnvironmentService" /> class.
/// </summary>
/// <param name="networkManager">The network manager.</param>
/// <exception cref="System.ArgumentNullException">networkManager</exception>
public EnvironmentService(INetworkManager networkManager)
public EnvironmentService(INetworkManager networkManager, IFileSystem fileSystem)
{
if (networkManager == null)
{
@ -110,6 +113,7 @@ namespace MediaBrowser.Api
}
_networkManager = networkManager;
_fileSystem = fileSystem;
}
/// <summary>
@ -117,8 +121,6 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">Path</exception>
/// <exception cref="System.ArgumentException"></exception>
public object Get(GetDirectoryContents request)
{
var path = request.Path;
@ -222,8 +224,7 @@ namespace MediaBrowser.Api
private IEnumerable<FileSystemEntryInfo> GetFileSystemEntries(GetDirectoryContents request)
{
// using EnumerateFileSystemInfos doesn't handle reparse points (symlinks)
var entries = new DirectoryInfo(request.Path).EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
.Concat<FileSystemInfo>(new DirectoryInfo(request.Path).EnumerateFiles("*", SearchOption.TopDirectoryOnly)).Where(i =>
var entries = _fileSystem.GetFileSystemEntries(request.Path).Where(i =>
{
if (!request.IncludeHidden && i.Attributes.HasFlag(FileAttributes.Hidden))
{

View file

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using CommonIO;
namespace MediaBrowser.Api.Images
{
@ -130,8 +131,7 @@ namespace MediaBrowser.Api.Images
{
try
{
return new DirectoryInfo(path)
.GetFiles("*", SearchOption.AllDirectories)
return _fileSystem.GetFiles(path)
.Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.Ordinal))
.Select(i => new ImageByNameInfo
{
@ -184,7 +184,7 @@ namespace MediaBrowser.Api.Images
var paths = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(_appPaths.GeneralPath, request.Name, filename + i)).ToList();
var path = paths.FirstOrDefault(File.Exists) ?? paths.FirstOrDefault();
var path = paths.FirstOrDefault(_fileSystem.FileExists) ?? paths.FirstOrDefault();
return ToStaticFileResult(path);
}
@ -198,11 +198,11 @@ namespace MediaBrowser.Api.Images
{
var themeFolder = Path.Combine(_appPaths.RatingsPath, request.Theme);
if (Directory.Exists(themeFolder))
if (_fileSystem.DirectoryExists(themeFolder))
{
var path = BaseItem.SupportedImageExtensions
.Select(i => Path.Combine(themeFolder, request.Name + i))
.FirstOrDefault(File.Exists);
.FirstOrDefault(_fileSystem.FileExists);
if (!string.IsNullOrEmpty(path))
{
@ -212,14 +212,14 @@ namespace MediaBrowser.Api.Images
var allFolder = Path.Combine(_appPaths.RatingsPath, "all");
if (Directory.Exists(allFolder))
if (_fileSystem.DirectoryExists(allFolder))
{
// Avoid implicitly captured closure
var currentRequest = request;
var path = BaseItem.SupportedImageExtensions
.Select(i => Path.Combine(allFolder, currentRequest.Name + i))
.FirstOrDefault(File.Exists);
.FirstOrDefault(_fileSystem.FileExists);
if (!string.IsNullOrEmpty(path))
{
@ -239,10 +239,10 @@ namespace MediaBrowser.Api.Images
{
var themeFolder = Path.Combine(_appPaths.MediaInfoImagesPath, request.Theme);
if (Directory.Exists(themeFolder))
if (_fileSystem.DirectoryExists(themeFolder))
{
var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, request.Name + i))
.FirstOrDefault(File.Exists);
.FirstOrDefault(_fileSystem.FileExists);
if (!string.IsNullOrEmpty(path))
{
@ -252,13 +252,13 @@ namespace MediaBrowser.Api.Images
var allFolder = Path.Combine(_appPaths.MediaInfoImagesPath, "all");
if (Directory.Exists(allFolder))
if (_fileSystem.DirectoryExists(allFolder))
{
// Avoid implicitly captured closure
var currentRequest = request;
var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, currentRequest.Name + i))
.FirstOrDefault(File.Exists);
.FirstOrDefault(_fileSystem.FileExists);
if (!string.IsNullOrEmpty(path))
{

View file

@ -17,6 +17,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Api.Images
@ -311,17 +312,23 @@ namespace MediaBrowser.Api.Images
{
try
{
var fileInfo = new FileInfo(info.Path);
int? width = null;
int? height = null;
long length = 0;
try
{
var size = _imageProcessor.GetImageSize(info);
if (info.IsLocalFile)
{
var fileInfo = new FileInfo(info.Path);
length = fileInfo.Length;
width = Convert.ToInt32(size.Width);
height = Convert.ToInt32(size.Height);
var size = _imageProcessor.GetImageSize(info);
width = Convert.ToInt32(size.Width);
height = Convert.ToInt32(size.Height);
}
}
catch
{
@ -333,7 +340,7 @@ namespace MediaBrowser.Api.Images
ImageIndex = imageIndex,
ImageType = info.Type,
ImageTag = _imageProcessor.GetImageCacheTag(item, info),
Size = fileInfo.Length,
Size = length,
Width = width,
Height = height
};
@ -557,7 +564,14 @@ namespace MediaBrowser.Api.Images
}).ToList() : new List<IImageEnhancer>();
var format = GetOutputFormat(request, imageInfo, supportedImageEnhancers);
var cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art;
if (request.CropWhitespace.HasValue)
{
cropwhitespace = request.CropWhitespace.Value;
}
var format = GetOutputFormat(request, imageInfo, cropwhitespace, supportedImageEnhancers);
var contentType = GetMimeType(format, imageInfo.Path);
var cacheGuid = new Guid(_imageProcessor.GetImageCacheTag(item, imageInfo, supportedImageEnhancers));
@ -578,6 +592,7 @@ namespace MediaBrowser.Api.Images
return GetImageResult(item,
request,
imageInfo,
cropwhitespace,
format,
supportedImageEnhancers,
contentType,
@ -590,6 +605,7 @@ namespace MediaBrowser.Api.Images
private async Task<object> GetImageResult(IHasImages item,
ImageRequest request,
ItemImageInfo image,
bool cropwhitespace,
ImageFormat format,
List<IImageEnhancer> enhancers,
string contentType,
@ -597,13 +613,6 @@ namespace MediaBrowser.Api.Images
IDictionary<string, string> headers,
bool isHeadRequest)
{
var cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art;
if (request.CropWhitespace.HasValue)
{
cropwhitespace = request.CropWhitespace.Value;
}
var options = new ImageProcessingOptions
{
CropWhiteSpace = cropwhitespace,
@ -637,7 +646,7 @@ namespace MediaBrowser.Api.Images
});
}
private ImageFormat GetOutputFormat(ImageRequest request, ItemImageInfo image, List<IImageEnhancer> enhancers)
private ImageFormat GetOutputFormat(ImageRequest request, ItemImageInfo image, bool cropwhitespace, List<IImageEnhancer> enhancers)
{
if (!string.IsNullOrWhiteSpace(request.Format))
{
@ -648,10 +657,37 @@ namespace MediaBrowser.Api.Images
}
}
var extension = Path.GetExtension(image.Path);
ImageFormat? inputFormat = null;
if (string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) ||
string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
{
inputFormat = ImageFormat.Jpg;
}
else if (string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase))
{
inputFormat = ImageFormat.Png;
}
var clientSupportedFormats = GetClientSupportedFormats();
if (inputFormat.HasValue && clientSupportedFormats.Contains(inputFormat.Value) && enhancers.Count == 0)
{
if ((request.Quality ?? 100) == 100 && !request.Height.HasValue && !request.Width.HasValue &&
!request.AddPlayedIndicator && !request.PercentPlayed.HasValue && !request.UnplayedCount.HasValue && string.IsNullOrWhiteSpace(request.BackgroundColor))
{
// TODO: Allow this when specfying max width/height if the value is in range
if (!cropwhitespace && !request.MaxHeight.HasValue && !request.MaxWidth.HasValue)
{
return inputFormat.Value;
}
}
}
var serverFormats = _imageProcessor.GetSupportedImageOutputFormats();
if (serverFormats.Contains(ImageFormat.Webp) &&
GetClientSupportedFormats().Contains(ImageFormat.Webp))
// Client doesn't care about format, so start with webp if supported
if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp))
{
return ImageFormat.Webp;
}
@ -661,10 +697,7 @@ namespace MediaBrowser.Api.Images
return ImageFormat.Png;
}
var extension = Path.GetExtension(image.Path);
if (string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) ||
string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
if (inputFormat.HasValue && inputFormat.Value == ImageFormat.Jpg)
{
return ImageFormat.Jpg;
}
@ -675,7 +708,7 @@ namespace MediaBrowser.Api.Images
private ImageFormat[] GetClientSupportedFormats()
{
var supportsWebP = (Request.AcceptTypes ?? new string[] {}).Contains("image/webp", StringComparer.OrdinalIgnoreCase);
var supportsWebP = (Request.AcceptTypes ?? new string[] { }).Contains("image/webp", StringComparer.OrdinalIgnoreCase);
var userAgent = Request.UserAgent ?? string.Empty;

View file

@ -16,6 +16,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api.Images
{
@ -237,7 +238,7 @@ namespace MediaBrowser.Api.Images
contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
}
if (File.Exists(contentPath))
if (_fileSystem.FileExists(contentPath))
{
return ToStaticFileResult(contentPath);
}
@ -281,7 +282,7 @@ namespace MediaBrowser.Api.Images
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath));
using (var stream = result.Content)
{
using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
@ -290,7 +291,7 @@ namespace MediaBrowser.Api.Images
}
}
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
using (var writer = new StreamWriter(pointerCachePath))
{
await writer.WriteAsync(fullCachePath).ConfigureAwait(false);

View file

@ -16,6 +16,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api
{
@ -200,7 +201,7 @@ namespace MediaBrowser.Api
//}
item.ProviderIds = request.ProviderIds;
var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions
var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(_fileSystem)
{
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
ImageRefreshMode = ImageRefreshMode.FullRefresh,
@ -230,7 +231,7 @@ namespace MediaBrowser.Api
contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
}
if (File.Exists(contentPath))
if (_fileSystem.FileExists(contentPath))
{
return ToStaticFileResult(contentPath);
}
@ -271,7 +272,7 @@ namespace MediaBrowser.Api
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath));
using (var stream = result.Content)
{
using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
@ -280,7 +281,7 @@ namespace MediaBrowser.Api
}
}
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
using (var writer = new StreamWriter(pointerCachePath))
{
await writer.WriteAsync(fullCachePath).ConfigureAwait(false);

View file

@ -4,6 +4,8 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Providers;
using ServiceStack;
using System.Threading;
using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Api
{
@ -37,11 +39,13 @@ namespace MediaBrowser.Api
{
private readonly ILibraryManager _libraryManager;
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
public ItemRefreshService(ILibraryManager libraryManager, IProviderManager providerManager)
public ItemRefreshService(ILibraryManager libraryManager, IProviderManager providerManager, IFileSystem fileSystem)
{
_libraryManager = libraryManager;
_providerManager = providerManager;
_fileSystem = fileSystem;
}
/// <summary>
@ -66,7 +70,7 @@ namespace MediaBrowser.Api
private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request)
{
return new MetadataRefreshOptions(new DirectoryService())
return new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
MetadataRefreshMode = request.MetadataRefreshMode,
ImageRefreshMode = request.ImageRefreshMode,

View file

@ -211,11 +211,6 @@ namespace MediaBrowser.Api
UpdateItem(request, item);
if (isLockedChanged && item.IsLocked)
{
item.IsUnidentified = false;
}
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
if (request.People != null)
@ -321,13 +316,8 @@ namespace MediaBrowser.Api
SetProductionLocations(item, request);
var hasLang = item as IHasPreferredMetadataLanguage;
if (hasLang != null)
{
hasLang.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
hasLang.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
}
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
var hasDisplayOrder = item as IHasDisplayOrder;
if (hasDisplayOrder != null)

View file

@ -3,6 +3,7 @@ using MediaBrowser.Controller;
using System;
using System.IO;
using System.Linq;
using CommonIO;
namespace MediaBrowser.Api.Library
{
@ -33,11 +34,11 @@ namespace MediaBrowser.Api.Library
var rootFolderPath = appPaths.DefaultUserViewsPath;
var path = Path.Combine(rootFolderPath, virtualFolderName);
if (!Directory.Exists(path))
if (!fileSystem.DirectoryExists(path))
{
throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
}
var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(shortcut))
@ -53,11 +54,9 @@ namespace MediaBrowser.Api.Library
/// <param name="virtualFolderName">Name of the virtual folder.</param>
/// <param name="path">The path.</param>
/// <param name="appPaths">The app paths.</param>
/// <exception cref="System.IO.DirectoryNotFoundException">The path does not exist.</exception>
/// <exception cref="System.ArgumentException">The path is not valid.</exception>
public static void AddMediaPath(IFileSystem fileSystem, string virtualFolderName, string path, IServerApplicationPaths appPaths)
{
if (!Directory.Exists(path))
if (!fileSystem.DirectoryExists(path))
{
throw new DirectoryNotFoundException("The path does not exist.");
}
@ -69,7 +68,7 @@ namespace MediaBrowser.Api.Library
var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
while (File.Exists(lnk))
while (fileSystem.FileExists(lnk))
{
shortcutFilename += "1";
lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);

View file

@ -27,6 +27,8 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Api.Library
{
@ -282,12 +284,13 @@ namespace MediaBrowser.Api.Library
private readonly IChannelManager _channelManager;
private readonly ITVSeriesManager _tvManager;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="LibraryService" /> class.
/// </summary>
public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager,
IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, IChannelManager channelManager, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor)
IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, IChannelManager channelManager, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem)
{
_itemRepo = itemRepo;
_libraryManager = libraryManager;
@ -301,6 +304,7 @@ namespace MediaBrowser.Api.Library
_channelManager = channelManager;
_tvManager = tvManager;
_libraryMonitor = libraryMonitor;
_fileSystem = fileSystem;
}
public object Get(GetSimilarItems request)
@ -557,7 +561,7 @@ namespace MediaBrowser.Api.Library
{
throw new ArgumentException("This command cannot be used for remote or virtual items.");
}
if (Directory.Exists(item.Path))
if (_fileSystem.DirectoryExists(item.Path))
{
throw new ArgumentException("This command cannot be used for directories.");
}
@ -719,7 +723,7 @@ namespace MediaBrowser.Api.Library
if (!item.CanDelete(user))
{
throw new UnauthorizedAccessException();
throw new SecurityException("Unauthorized access");
}
if (item is ILiveTvRecording)

View file

@ -10,6 +10,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api.Library
{
@ -46,6 +47,12 @@ namespace MediaBrowser.Api.Library
/// </summary>
/// <value><c>true</c> if [refresh library]; otherwise, <c>false</c>.</value>
public bool RefreshLibrary { get; set; }
/// <summary>
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
public string[] Paths { get; set; }
}
[Route("/Library/VirtualFolders", "DELETE")]
@ -195,22 +202,42 @@ namespace MediaBrowser.Api.Library
var virtualFolderPath = Path.Combine(rootFolderPath, name);
if (Directory.Exists(virtualFolderPath))
if (_fileSystem.DirectoryExists(virtualFolderPath))
{
throw new ArgumentException("There is already a media collection with the name " + name + ".");
throw new ArgumentException("There is already a media library with the name " + name + ".");
}
if (request.Paths != null)
{
var invalidpath = request.Paths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i));
if (invalidpath != null)
{
throw new ArgumentException("The specified path does not exist: " + invalidpath + ".");
}
}
_libraryMonitor.Stop();
try
{
Directory.CreateDirectory(virtualFolderPath);
_fileSystem.CreateDirectory(virtualFolderPath);
if (!string.IsNullOrEmpty(request.CollectionType))
{
var path = Path.Combine(virtualFolderPath, request.CollectionType + ".collection");
File.Create(path);
using (File.Create(path))
{
}
}
if (request.Paths != null)
{
foreach (var path in request.Paths)
{
LibraryHelpers.AddMediaPath(_fileSystem, request.Name, path, _appPaths);
}
}
}
finally
@ -256,12 +283,12 @@ namespace MediaBrowser.Api.Library
var currentPath = Path.Combine(rootFolderPath, request.Name);
var newPath = Path.Combine(rootFolderPath, request.NewName);
if (!Directory.Exists(currentPath))
if (!_fileSystem.DirectoryExists(currentPath))
{
throw new DirectoryNotFoundException("The media collection does not exist");
}
if (!string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase) && Directory.Exists(newPath))
if (!string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase) && _fileSystem.DirectoryExists(newPath))
{
throw new ArgumentException("There is already a media collection with the name " + newPath + ".");
}
@ -276,11 +303,11 @@ namespace MediaBrowser.Api.Library
//Create an unique name
var temporaryName = Guid.NewGuid().ToString();
var temporaryPath = Path.Combine(rootFolderPath, temporaryName);
Directory.Move(currentPath, temporaryPath);
_fileSystem.MoveDirectory(currentPath, temporaryPath);
currentPath = temporaryPath;
}
Directory.Move(currentPath, newPath);
_fileSystem.MoveDirectory(currentPath, newPath);
}
finally
{
@ -319,7 +346,7 @@ namespace MediaBrowser.Api.Library
var path = Path.Combine(rootFolderPath, request.Name);
if (!Directory.Exists(path))
if (!_fileSystem.DirectoryExists(path))
{
throw new DirectoryNotFoundException("The media folder does not exist");
}

View file

@ -521,12 +521,12 @@ namespace MediaBrowser.Api.LiveTv
if (user == null)
{
throw new UnauthorizedAccessException("Anonymous live tv management is not allowed.");
throw new SecurityException("Anonymous live tv management is not allowed.");
}
if (!user.Policy.EnableLiveTvManagement)
{
throw new UnauthorizedAccessException("The current user does not have permission to manage live tv.");
throw new SecurityException("The current user does not have permission to manage live tv.");
}
}

View file

@ -11,10 +11,9 @@
<AssemblyName>MediaBrowser.Api</AssemblyName>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<RestorePackages>true</RestorePackages>
<ReleaseVersion>
</ReleaseVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -25,7 +24,6 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
@ -34,7 +32,6 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
<DebugType>none</DebugType>
@ -43,15 +40,17 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<Reference Include="MoreLinq, Version=1.1.17511.0, Culture=neutral, PublicKeyToken=384d532d7e88985d, processorArchitecture=MSIL">
<Reference Include="CommonIO, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\morelinq.1.1.0\lib\net35\MoreLinq.dll</HintPath>
<HintPath>..\packages\CommonIO.1.0.0.5\lib\net45\CommonIO.dll</HintPath>
</Reference>
<Reference Include="Patterns.Logging">
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@ -64,12 +63,14 @@
<Reference Include="ServiceStack.Text">
<HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
</Reference>
<Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.1.1\lib\net35\MoreLinq.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="AppThemeService.cs" />
<Compile Include="BrandingService.cs" />
<Compile Include="ChannelService.cs" />
<Compile Include="ConnectService.cs" />

View file

@ -23,6 +23,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api.Playback
{
@ -290,13 +291,6 @@ namespace MediaBrowser.Api.Playback
{
get
{
var lib = ApiEntryPoint.Instance.GetEncodingOptions().H264Encoder;
if (!string.IsNullOrWhiteSpace(lib))
{
return lib;
}
return "libx264";
}
}
@ -809,6 +803,46 @@ namespace MediaBrowser.Api.Playback
return "copy";
}
/// <summary>
/// Gets the name of the output video codec
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected string GetVideoDecoder(StreamState state)
{
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareVideoDecoder, "qsv", StringComparison.OrdinalIgnoreCase))
{
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
{
switch (state.MediaSource.VideoStream.Codec.ToLower())
{
case "avc":
case "h264":
if (MediaEncoder.SupportsDecoder("h264_qsv"))
{
return "-c:v h264_qsv ";
}
break;
case "mpeg2video":
if (MediaEncoder.SupportsDecoder("mpeg2_qsv"))
{
return "-c:v mpeg2_qsv ";
}
break;
case "vc1":
if (MediaEncoder.SupportsDecoder("vc1_qsv"))
{
return "-c:v vc1_qsv ";
}
break;
}
}
}
// leave blank so ffmpeg will decide
return null;
}
/// <summary>
/// Gets the input argument.
/// </summary>
@ -816,7 +850,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetInputArgument(StreamState state)
{
var arg = "-i " + GetInputPathArgument(state);
var arg = string.Format("-i {0}", GetInputPathArgument(state));
if (state.SubtitleStream != null)
{
@ -826,7 +860,7 @@ namespace MediaBrowser.Api.Playback
}
}
return arg;
return arg.Trim();
}
private string GetInputPathArgument(StreamState state)
@ -840,7 +874,7 @@ namespace MediaBrowser.Api.Playback
{
if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
{
inputPath = MediaEncoderHelpers.GetInputArgument(mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
}
}
@ -889,7 +923,7 @@ namespace MediaBrowser.Api.Playback
CancellationTokenSource cancellationTokenSource,
string workingDirectory = null)
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
FileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
@ -942,7 +976,7 @@ namespace MediaBrowser.Api.Playback
Logger.Info(commandLineLogMessage);
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt");
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
@ -972,7 +1006,7 @@ namespace MediaBrowser.Api.Playback
StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream);
// Wait for the file to exist before proceeeding
while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
{
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
}
@ -1027,6 +1061,7 @@ namespace MediaBrowser.Api.Playback
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
await target.FlushAsync().ConfigureAwait(false);
}
}
}
@ -1151,15 +1186,8 @@ namespace MediaBrowser.Api.Playback
if (bitrate.HasValue)
{
var hasFixedResolution = state.VideoRequest.HasFixedResolution;
if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
{
if (hasFixedResolution)
{
return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
}
// With vpx when crf is used, b:v becomes a max rate
// https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
@ -1170,36 +1198,15 @@ namespace MediaBrowser.Api.Playback
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
}
// h264_qsv
if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
// h264
if (isHls)
{
if (hasFixedResolution)
{
if (isHls)
{
return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
}
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
}
return string.Format(" -b:v {0} -maxrate ({0}*1.2) -bufsize ({0}*2)", bitrate.Value.ToString(UsCulture));
return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
bitrate.Value.ToString(UsCulture),
(bitrate.Value * 2).ToString(UsCulture));
}
// H264
if (hasFixedResolution)
{
if (isHls)
{
return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
}
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
}
return string.Format(" -maxrate {0} -bufsize {1}",
bitrate.Value.ToString(UsCulture),
(bitrate.Value * 2).ToString(UsCulture));
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
}
return string.Empty;
@ -1986,7 +1993,8 @@ namespace MediaBrowser.Api.Playback
state.IsTargetCabac,
state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount);
state.TargetAudioStreamCount,
state.TargetVideoCodecTag);
if (mediaProfile != null)
{
@ -2083,7 +2091,8 @@ namespace MediaBrowser.Api.Playback
state.IsTargetCabac,
state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount
state.TargetAudioStreamCount,
state.TargetVideoCodecTag
).FirstOrDefault() ?? string.Empty;
}
@ -2158,6 +2167,12 @@ namespace MediaBrowser.Api.Playback
inputModifier += " -re";
}
var videoDecoder = GetVideoDecoder(state);
if (!string.IsNullOrWhiteSpace(videoDecoder))
{
inputModifier += " " + videoDecoder;
}
return inputModifier;
}

View file

@ -17,6 +17,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Api.Playback.Dash
@ -174,7 +175,7 @@ namespace MediaBrowser.Api.Playback.Dash
var workingDirectory = Path.Combine(Path.GetDirectoryName(playlistPath), (startNumber == -1 ? 0 : startNumber).ToString(CultureInfo.InvariantCulture));
state.WaitForPath = Path.Combine(workingDirectory, Path.GetFileName(playlistPath));
Directory.CreateDirectory(workingDirectory);
FileSystem.CreateDirectory(workingDirectory);
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource, workingDirectory).ConfigureAwait(false);
await WaitForMinimumDashSegmentCount(Path.Combine(workingDirectory, Path.GetFileName(playlistPath)), 1, cancellationTokenSource.Token).ConfigureAwait(false);
}
@ -322,14 +323,13 @@ namespace MediaBrowser.Api.Playback.Dash
}
}
private static List<FileInfo> GetLastTranscodingFiles(string playlist, string segmentExtension, IFileSystem fileSystem, int count)
private static List<FileSystemMetadata> GetLastTranscodingFiles(string playlist, string segmentExtension, IFileSystem fileSystem, int count)
{
var folder = Path.GetDirectoryName(playlist);
try
{
return new DirectoryInfo(folder)
.EnumerateFiles("*", SearchOption.AllDirectories)
return fileSystem.GetFiles(folder)
.Where(i => string.Equals(i.Extension, segmentExtension, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(fileSystem.GetLastWriteTimeUtc)
.Take(count)
@ -337,7 +337,7 @@ namespace MediaBrowser.Api.Playback.Dash
}
catch (DirectoryNotFoundException)
{
return new List<FileInfo>();
return new List<FileSystemMetadata>();
}
}
@ -348,20 +348,20 @@ namespace MediaBrowser.Api.Playback.Dash
if (requestedIndex == -1)
{
var path = Path.Combine(folder, "0", "stream" + representationId + "-" + "init" + segmentExtension);
return File.Exists(path) ? path : null;
return FileSystem.FileExists(path) ? path : null;
}
try
{
foreach (var subfolder in new DirectoryInfo(folder).EnumerateDirectories().ToList())
foreach (var subfolder in FileSystem.GetDirectoryPaths(folder).ToList())
{
var subfolderName = Path.GetFileNameWithoutExtension(subfolder.FullName);
var subfolderName = Path.GetFileNameWithoutExtension(subfolder);
int startNumber;
if (int.TryParse(subfolderName, NumberStyles.Any, UsCulture, out startNumber))
{
var segmentIndex = requestedIndex - startNumber + 1;
var path = Path.Combine(folder, subfolderName, "stream" + representationId + "-" + segmentIndex.ToString("00000", CultureInfo.InvariantCulture) + segmentExtension);
if (File.Exists(path))
if (FileSystem.FileExists(path))
{
return path;
}

View file

@ -15,6 +15,7 @@ using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api.Playback.Hls
{
@ -23,7 +24,8 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public abstract class BaseHlsService : BaseStreamingService
{
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer)
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer)
: base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer)
{
}
@ -90,12 +92,12 @@ namespace MediaBrowser.Api.Playback.Hls
TranscodingJob job = null;
var playlist = state.OutputFilePath;
if (!File.Exists(playlist))
if (!FileSystem.FileExists(playlist))
{
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try
{
if (!File.Exists(playlist))
if (!FileSystem.FileExists(playlist))
{
// If the playlist doesn't already exist, startup ffmpeg
try
@ -150,7 +152,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
}
return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
@ -317,4 +319,4 @@ namespace MediaBrowser.Api.Playback.Hls
return false;
}
}
}
}

View file

@ -20,6 +20,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Api.Playback.Hls
@ -165,7 +166,7 @@ namespace MediaBrowser.Api.Playback.Hls
TranscodingJob job = null;
if (File.Exists(segmentPath))
if (FileSystem.FileExists(segmentPath))
{
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
@ -174,7 +175,7 @@ namespace MediaBrowser.Api.Playback.Hls
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try
{
if (File.Exists(segmentPath))
if (FileSystem.FileExists(segmentPath))
{
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
@ -354,7 +355,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
private void DeleteFile(FileInfo file, int retryCount)
private void DeleteFile(FileSystemMetadata file, int retryCount)
{
if (retryCount >= 5)
{
@ -378,7 +379,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
private static FileInfo GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem)
private static FileSystemMetadata GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem)
{
var folder = Path.GetDirectoryName(playlist);
@ -386,8 +387,7 @@ namespace MediaBrowser.Api.Playback.Hls
try
{
return new DirectoryInfo(folder)
.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
return fileSystem.GetFiles(folder)
.Where(i => string.Equals(i.Extension, segmentExtension, StringComparison.OrdinalIgnoreCase) && Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(fileSystem.GetLastWriteTimeUtc)
.FirstOrDefault();
@ -432,7 +432,7 @@ namespace MediaBrowser.Api.Playback.Hls
CancellationToken cancellationToken)
{
// If all transcoding has completed, just return immediately
if (transcodingJob != null && transcodingJob.HasExited && File.Exists(segmentPath))
if (transcodingJob != null && transcodingJob.HasExited && FileSystem.FileExists(segmentPath))
{
return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
}
@ -452,7 +452,7 @@ namespace MediaBrowser.Api.Playback.Hls
// If it appears in the playlist, it's done
if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
{
if (File.Exists(segmentPath))
if (FileSystem.FileExists(segmentPath))
{
return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
}
@ -989,4 +989,4 @@ namespace MediaBrowser.Api.Playback.Hls
return base.CanStreamCopyVideo(request, videoStream);
}
}
}
}

View file

@ -8,6 +8,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using ServiceStack;
using System;
using CommonIO;
namespace MediaBrowser.Api.Playback.Hls
{

View file

@ -57,7 +57,7 @@ namespace MediaBrowser.Api.Playback
Size = 102400;
}
}
[Authenticated]
public class MediaInfoService : BaseApiService
{
@ -289,7 +289,7 @@ namespace MediaBrowser.Api.Playback
if (mediaSource.SupportsDirectStream)
{
options.MaxBitrate = GetMaxBitrate(maxBitrate);
// The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
streamBuilder.BuildAudioItem(options) :
@ -309,7 +309,7 @@ namespace MediaBrowser.Api.Playback
if (mediaSource.SupportsTranscoding)
{
options.MaxBitrate = GetMaxBitrate(maxBitrate);
// The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
streamBuilder.BuildAudioItem(options) :
@ -336,9 +336,15 @@ namespace MediaBrowser.Api.Playback
var maxBitrate = clientMaxBitrate;
var remoteClientMaxBitrate = _config.Configuration.RemoteClientBitrateLimit;
if (remoteClientMaxBitrate > 0 && !_networkManager.IsInLocalNetwork(Request.RemoteIp))
if (remoteClientMaxBitrate > 0)
{
maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate);
var isInLocalNetwork = _networkManager.IsInLocalNetwork(Request.RemoteIp);
Logger.Info("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, Request.RemoteIp, isInLocalNetwork);
if (!isInLocalNetwork)
{
maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate);
}
}
return maxBitrate;

View file

@ -11,6 +11,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using ServiceStack;
using System.Collections.Generic;
using CommonIO;
namespace MediaBrowser.Api.Playback.Progressive
{

View file

@ -17,6 +17,7 @@ using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api.Playback.Progressive
{
@ -139,7 +140,7 @@ namespace MediaBrowser.Api.Playback.Progressive
}
var outputPath = state.OutputFilePath;
var outputPathExists = File.Exists(outputPath);
var outputPathExists = FileSystem.FileExists(outputPath);
var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive);
@ -325,7 +326,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{
TranscodingJob job;
if (!File.Exists(outputPath))
if (!FileSystem.FileExists(outputPath))
{
job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
}

View file

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api.Playback.Progressive
{

View file

@ -11,12 +11,14 @@ using MediaBrowser.Model.Serialization;
using ServiceStack;
using System;
using System.IO;
using CommonIO;
namespace MediaBrowser.Api.Playback.Progressive
{
/// <summary>
/// Class GetVideoStream
/// </summary>
[Route("/Videos/{Id}/stream.mpegts", "GET")]
[Route("/Videos/{Id}/stream.ts", "GET")]
[Route("/Videos/{Id}/stream.webm", "GET")]
[Route("/Videos/{Id}/stream.asf", "GET")]

View file

@ -74,7 +74,7 @@ namespace MediaBrowser.Api.Playback
{
get
{
return ReadInputAtNativeFramerate ? 1000 : 0;
return 0;
}
}
@ -457,6 +457,17 @@ namespace MediaBrowser.Api.Playback
}
}
public string TargetVideoCodecTag
{
get
{
var stream = VideoStream;
return !Request.Static
? null
: stream == null ? null : stream.CodecTag;
}
}
public bool? IsTargetAnamorphic
{
get

View file

@ -40,10 +40,27 @@ namespace MediaBrowser.Api
/// Gets or sets the user id.
/// </summary>
/// <value>The user id.</value>
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string UserId { get; set; }
}
[Route("/Playlists/{Id}/Items/{ItemId}/Move/{NewIndex}", "POST", Summary = "Moves a playlist item")]
public class MoveItem : IReturnVoid
{
[ApiMember(Name = "ItemId", Description = "ItemId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string ItemId { get; set; }
[ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Id { get; set; }
/// <summary>
/// Gets or sets the user id.
/// </summary>
/// <value>The user id.</value>
[ApiMember(Name = "NewIndex", Description = "NewIndex", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public int NewIndex { get; set; }
}
[Route("/Playlists/{Id}/Items", "DELETE", Summary = "Removes items from a playlist")]
public class RemoveFromPlaylist : IReturnVoid
{
@ -105,6 +122,13 @@ namespace MediaBrowser.Api
_libraryManager = libraryManager;
}
public void Post(MoveItem request)
{
var task = _playlistManager.MoveItem(request.Id, request.ItemId, request.NewIndex);
Task.WaitAll(task);
}
public async Task<object> Post(CreatePlaylist request)
{
var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest

View file

@ -118,6 +118,14 @@ namespace MediaBrowser.Api
public string Name { get; set; }
}
[Route("/Appstore/Register", "POST", Summary = "Registers an appstore sale")]
[Authenticated]
public class RegisterAppstoreSale
{
[ApiMember(Name = "Parameters", Description = "Java representation of parameters to pass through to admin server", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Parameters { get; set; }
}
/// <summary>
/// Class PluginsService
/// </summary>
@ -265,6 +273,16 @@ namespace MediaBrowser.Api
return ToOptimizedSerializedResultUsingCache(result);
}
/// <summary>
/// Post app store sale
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task Post(RegisterAppstoreSale request)
{
await _securityManager.RegisterAppStoreSale(request.Parameters);
}
/// <summary>
/// Posts the specified request.
/// </summary>

View file

@ -43,7 +43,6 @@ namespace MediaBrowser.Api.Reports
MusicArtist,
AudioAlbum,
Locked,
Unidentified,
ImagePrimary,
ImageBackdrop,
ImageLogo,

View file

@ -17,7 +17,6 @@ namespace MediaBrowser.Api.Reports
TrailersImage,
SpecialsImage,
LockDataImage,
UnidentifiedImage,
TagsPrimaryImage,
TagsBackdropImage,
TagsLogoImage,

View file

@ -105,7 +105,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -122,7 +121,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -143,7 +141,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -161,7 +158,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -177,7 +173,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -198,7 +193,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -223,7 +217,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -241,7 +234,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -260,7 +252,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -284,7 +275,6 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -315,11 +305,9 @@ namespace MediaBrowser.Api.Reports
{
HeaderMetadata.Status,
HeaderMetadata.Locked,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
HeaderMetadata.Unidentified,
HeaderMetadata.ImagePrimary,
HeaderMetadata.ImageBackdrop,
HeaderMetadata.ImageLogo,
@ -376,12 +364,6 @@ namespace MediaBrowser.Api.Reports
option.Header.CanGroup = false;
option.Header.DisplayType = ReportDisplayType.Export;
break;
case HeaderMetadata.Unidentified:
option.Column = (i, r) => this.GetBoolString(r.IsUnidentified);
option.Header.ItemViewType = ItemViewType.UnidentifiedImage;
option.Header.CanGroup = false;
option.Header.DisplayType = ReportDisplayType.Export;
break;
case HeaderMetadata.ImagePrimary:
option.Column = (i, r) => this.GetBoolString(r.HasImageTagsPrimary);
option.Header.ItemViewType = ItemViewType.TagsPrimaryImage;
@ -633,7 +615,6 @@ namespace MediaBrowser.Api.Reports
{
Id = item.Id.ToString("N"),
HasLockData = item.IsLocked,
IsUnidentified = item.IsUnidentified,
HasLocalTrailer = hasTrailers != null ? hasTrailers.GetTrailerIds().Count() > 0 : false,
HasImageTagsPrimary = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Primary) > 0),
HasImageTagsBackdrop = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Backdrop) > 0),

View file

@ -56,10 +56,6 @@ namespace MediaBrowser.Api.Reports
/// <value> true if this object has specials, false if not. </value>
public bool HasSpecials { get; set; }
/// <summary> Gets or sets a value indicating whether this object is unidentified. </summary>
/// <value> true if this object is unidentified, false if not. </value>
public bool IsUnidentified { get; set; }
/// <summary> Gets or sets the columns. </summary>
/// <value> The columns. </value>
public List<ReportItem> Columns { get; set; }

View file

@ -226,7 +226,6 @@ namespace MediaBrowser.Api.Reports
NameStartsWithOrGreater = request.NameStartsWithOrGreater,
HasImdbId = request.HasImdbId,
IsYearMismatched = request.IsYearMismatched,
IsUnidentified = request.IsUnidentified,
IsPlaceHolder = request.IsPlaceHolder,
IsLocked = request.IsLocked,
IsInBoxSet = request.IsInBoxSet,

View file

@ -213,7 +213,7 @@ namespace MediaBrowser.Api.Reports
};
foreach (var item in t)
{
var ps = items.Where(x => x.People != null && x.SupportsPeople).SelectMany(x => x.People)
var ps = items.SelectMany(x => _libraryManager.GetPeople(x))
.Where(n => n.Type == item.ToString())
.GroupBy(x => x.Name)
.OrderByDescending(x => x.Count())

View file

@ -124,7 +124,7 @@ namespace MediaBrowser.Api.Social
Task.WaitAll(task);
}
public object Get(GetShareImage request)
public async Task<object> Get(GetShareImage request)
{
var share = _sharingManager.GetShareInfo(request.Id);
@ -143,7 +143,21 @@ namespace MediaBrowser.Api.Social
if (image != null)
{
return ToStaticFileResult(image.Path);
if (image.IsLocalFile)
{
return ToStaticFileResult(image.Path);
}
try
{
// Don't fail the request over this
var updatedImage = await _libraryManager.ConvertImageToLocal(item, image, 0).ConfigureAwait(false);
return ToStaticFileResult(updatedImage.Path);
}
catch
{
}
}
// Grab a dlna icon if nothing else is available

View file

@ -69,10 +69,11 @@ namespace MediaBrowser.Api
_config.Configuration.MergeMetadataAndImagesByName = true;
_config.Configuration.EnableStandaloneMetadata = true;
_config.Configuration.EnableLibraryMetadataSubFolder = true;
_config.Configuration.EnableUserSpecificUserViews = true;
_config.Configuration.EnableCustomPathSubFolders = true;
_config.Configuration.DisableXmlSavers = true;
_config.Configuration.DisableStartupScan = true;
_config.Configuration.EnableUserViews = true;
_config.Configuration.EnableDateLastRefresh = true;
_config.SaveConfiguration();
}

View file

@ -15,7 +15,9 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Api.Subtitles
{
@ -127,14 +129,16 @@ namespace MediaBrowser.Api.Subtitles
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProviderManager providerManager)
public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProviderManager providerManager, IFileSystem fileSystem)
{
_libraryManager = libraryManager;
_subtitleManager = subtitleManager;
_subtitleEncoder = subtitleEncoder;
_mediaSourceManager = mediaSourceManager;
_providerManager = providerManager;
_fileSystem = fileSystem;
}
public async Task<object> Get(GetSubtitlePlaylist request)
@ -259,7 +263,7 @@ namespace MediaBrowser.Api.Subtitles
await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None)
.ConfigureAwait(false);
_providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions());
_providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(_fileSystem));
}
catch (Exception ex)
{

View file

@ -244,7 +244,15 @@ namespace MediaBrowser.Api.Sync
var task = _syncManager.ReportSyncJobItemTransferBeginning(request.Id);
Task.WaitAll(task);
return ToStaticFileResult(jobItem.OutputPath);
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
Path = jobItem.OutputPath,
OnError = () =>
{
var failedTask = _syncManager.ReportSyncJobItemTransferFailed(request.Id);
Task.WaitAll(failedTask);
}
});
}
public object Get(GetSyncDialogOptions request)

View file

@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api.System
{
@ -118,18 +119,17 @@ namespace MediaBrowser.Api.System
public object Get(GetServerLogs request)
{
List<FileInfo> files;
List<FileSystemMetadata> files;
try
{
files = new DirectoryInfo(_appPaths.LogDirectoryPath)
.EnumerateFiles("*", SearchOption.AllDirectories)
files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
.Where(i => string.Equals(i.Extension, ".txt", StringComparison.OrdinalIgnoreCase))
.ToList();
}
catch (DirectoryNotFoundException)
{
files = new List<FileInfo>();
files = new List<FileSystemMetadata>();
}
var result = files.Select(i => new LogFile
@ -149,8 +149,7 @@ namespace MediaBrowser.Api.System
public object Get(GetLogFile request)
{
var file = new DirectoryInfo(_appPaths.LogDirectoryPath)
.EnumerateFiles("*", SearchOption.AllDirectories)
var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
.First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite);

View file

@ -299,9 +299,6 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "IsLocked", Description = "Optional filter by items that are locked.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public bool? IsLocked { get; set; }
[ApiMember(Name = "IsUnidentified", Description = "Optional filter by items that are unidentified by internet metadata providers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public bool? IsUnidentified { get; set; }
[ApiMember(Name = "IsPlaceHolder", Description = "Optional filter by items that are placeholders", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public bool? IsPlaceHolder { get; set; }

View file

@ -192,7 +192,6 @@ namespace MediaBrowser.Api.UserLibrary
NameStartsWithOrGreater = request.NameStartsWithOrGreater,
HasImdbId = request.HasImdbId,
IsYearMismatched = request.IsYearMismatched,
IsUnidentified = request.IsUnidentified,
IsPlaceHolder = request.IsPlaceHolder,
IsLocked = request.IsLocked,
IsInBoxSet = request.IsInBoxSet,

View file

@ -39,6 +39,17 @@ namespace MediaBrowser.Api.UserLibrary
public string UserId { get; set; }
}
[Route("/Users/{UserId}/GroupingOptions", "GET")]
public class GetGroupingOptions : IReturn<List<SpecialViewOption>>
{
/// <summary>
/// Gets or sets the user id.
/// </summary>
/// <value>The user id.</value>
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string UserId { get; set; }
}
public class UserViewsService : BaseApiService
{
private readonly IUserManager _userManager;
@ -105,6 +116,29 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(list);
}
public async Task<object> Get(GetGroupingOptions request)
{
var user = _userManager.GetUserById(request.UserId);
var views = user.RootFolder
.GetChildren(user, true)
.OfType<Folder>()
.Where(i => !UserView.IsExcludedFromGrouping(i))
.ToList();
var list = views
.Select(i => new SpecialViewOption
{
Name = i.Name,
Id = i.Id.ToString("N")
})
.OrderBy(i => i.Name)
.ToList();
return ToOptimizedResult(list);
}
private bool IsEligibleForSpecialView(ICollectionFolder view)
{
var types = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Games, CollectionType.Music, CollectionType.Photos };

View file

@ -11,6 +11,7 @@ using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api
{

View file

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="morelinq" version="1.1.0" targetFramework="net45" />
<package id="CommonIO" version="1.0.0.5" targetFramework="net45" />
<package id="morelinq" version="1.1.1" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
</packages>

View file

@ -7,6 +7,8 @@ using SharpCompress.Reader;
using SharpCompress.Reader.Zip;
using System;
using System.IO;
using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Common.Implementations.Archiving
{
@ -15,7 +17,14 @@ namespace MediaBrowser.Common.Implementations.Archiving
/// </summary>
public class ZipClient : IZipClient
{
/// <summary>
private IFileSystem _fileSystem;
public ZipClient(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
/// <summary>
/// Extracts all.
/// </summary>
/// <param name="sourceFile">The source file.</param>
@ -23,7 +32,7 @@ namespace MediaBrowser.Common.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
using (var fileStream = _fileSystem.OpenRead(sourceFile))
{
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
}
@ -73,7 +82,7 @@ namespace MediaBrowser.Common.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
using (var fileStream = _fileSystem.OpenRead(sourceFile))
{
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
}
@ -112,7 +121,7 @@ namespace MediaBrowser.Common.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
using (var fileStream = _fileSystem.OpenRead(sourceFile))
{
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
}
@ -150,7 +159,7 @@ namespace MediaBrowser.Common.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromRar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
using (var fileStream = _fileSystem.OpenRead(sourceFile))
{
ExtractAllFromRar(fileStream, targetPath, overwriteExistingFiles);
}

View file

@ -30,6 +30,7 @@ using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Common.Implementations
{
@ -93,7 +94,7 @@ namespace MediaBrowser.Common.Implementations
/// <summary>
/// The _XML serializer
/// </summary>
protected readonly IXmlSerializer XmlSerializer = new XmlSerializer();
protected readonly IXmlSerializer XmlSerializer;
/// <summary>
/// Gets assemblies that failed to load
@ -180,7 +181,7 @@ namespace MediaBrowser.Common.Implementations
{
if (_deviceId == null)
{
_deviceId = new DeviceId(ApplicationPaths, LogManager.GetLogger("SystemId"));
_deviceId = new DeviceId(ApplicationPaths, LogManager.GetLogger("SystemId"), FileSystemManager);
}
return _deviceId.Value;
@ -199,6 +200,7 @@ namespace MediaBrowser.Common.Implementations
ILogManager logManager,
IFileSystem fileSystem)
{
XmlSerializer = new MediaBrowser.Common.Implementations.Serialization.XmlSerializer (fileSystem);
FailedAssemblies = new List<string>();
ApplicationPaths = applicationPaths;
@ -320,7 +322,7 @@ namespace MediaBrowser.Common.Implementations
protected virtual IJsonSerializer CreateJsonSerializer()
{
return new JsonSerializer();
return new JsonSerializer(FileSystemManager);
}
private void SetHttpLimit()
@ -414,6 +416,8 @@ namespace MediaBrowser.Common.Implementations
/// </summary>
protected virtual void FindParts()
{
RegisterModules();
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
Plugins = GetExports<IPlugin>();
}
@ -449,7 +453,7 @@ namespace MediaBrowser.Common.Implementations
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, Logger);
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, Logger, FileSystemManager);
RegisterSingleInstance(JsonSerializer);
RegisterSingleInstance(XmlSerializer);
@ -473,13 +477,12 @@ namespace MediaBrowser.Common.Implementations
InstallationManager = new InstallationManager(Logger, this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager);
RegisterSingleInstance(InstallationManager);
ZipClient = new ZipClient();
ZipClient = new ZipClient(FileSystemManager);
RegisterSingleInstance(ZipClient);
IsoManager = new IsoManager();
RegisterSingleInstance(IsoManager);
RegisterModules();
return Task.FromResult (true);
}
@ -522,6 +525,14 @@ namespace MediaBrowser.Common.Implementations
}
catch (ReflectionTypeLoadException ex)
{
if (ex.LoaderExceptions != null)
{
foreach (var loaderException in ex.LoaderExceptions)
{
Logger.Error("LoaderException: " + loaderException.Message);
}
}
// If it fails we can still get a list of the Types it was able to resolve
return ex.Types.Where(t => t != null);
}
@ -581,7 +592,7 @@ namespace MediaBrowser.Common.Implementations
protected void RegisterSingleInstance<T>(T obj, bool manageLifetime = true)
where T : class
{
Container.RegisterSingle(obj);
Container.RegisterSingleton(obj);
if (manageLifetime)
{
@ -607,7 +618,7 @@ namespace MediaBrowser.Common.Implementations
protected void RegisterSingleInstance<T>(Func<T> func)
where T : class
{
Container.RegisterSingle(func);
Container.RegisterSingleton(func);
}
void IDependencyContainer.Register(Type typeInterface, Type typeImplementation)

View file

@ -9,6 +9,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using CommonIO;
using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Common.Implementations.Configuration
{
@ -32,7 +34,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
/// Occurs when [configuration updating].
/// </summary>
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdating;
/// <summary>
/// Occurs when [named configuration updated].
/// </summary>
@ -54,6 +56,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
/// </summary>
/// <value>The application paths.</value>
public IApplicationPaths CommonApplicationPaths { get; private set; }
public readonly IFileSystem FileSystem;
/// <summary>
/// The _configuration loaded
@ -87,8 +90,8 @@ namespace MediaBrowser.Common.Implementations.Configuration
}
}
private ConfigurationStore[] _configurationStores = {};
private IConfigurationFactory[] _configurationFactories = {};
private ConfigurationStore[] _configurationStores = { };
private IConfigurationFactory[] _configurationFactories = { };
/// <summary>
/// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
@ -96,10 +99,11 @@ namespace MediaBrowser.Common.Implementations.Configuration
/// <param name="applicationPaths">The application paths.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="xmlSerializer">The XML serializer.</param>
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILogManager logManager, IXmlSerializer xmlSerializer)
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILogManager logManager, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
{
CommonApplicationPaths = applicationPaths;
XmlSerializer = xmlSerializer;
FileSystem = fileSystem;
Logger = logManager.GetLogger(GetType().Name);
UpdateCachePath();
@ -199,9 +203,19 @@ namespace MediaBrowser.Common.Implementations.Configuration
{
throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
}
EnsureWriteAccess(newPath);
}
}
protected void EnsureWriteAccess(string path)
{
var file = Path.Combine(path, Guid.NewGuid().ToString());
FileSystem.WriteAllText(file, string.Empty);
FileSystem.DeleteFile(file);
}
private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
private string GetConfigurationFile(string key)
@ -215,9 +229,15 @@ namespace MediaBrowser.Common.Implementations.Configuration
{
var file = GetConfigurationFile(key);
var configurationType = _configurationStores
.First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase))
.ConfigurationType;
var configurationInfo = _configurationStores
.FirstOrDefault(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase));
if (configurationInfo == null)
{
throw new ResourceNotFoundException("Configuration with key " + key + " not found.");
}
var configurationType = configurationInfo.ConfigurationType;
lock (_configurationSyncLock)
{
@ -272,7 +292,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
NewConfiguration = configuration
}, Logger);
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
var path = GetConfigurationFile(key);

View file

@ -2,6 +2,7 @@
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Common.Implementations.Configuration
{
@ -27,7 +28,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
// Use try/catch to avoid the extra file system lookup using File.Exists
try
{
buffer = File.ReadAllBytes(path);
buffer = File.ReadAllBytes(path);
configuration = xmlSerializer.DeserializeFromBytes(type, buffer);
}
@ -46,10 +47,10 @@ namespace MediaBrowser.Common.Implementations.Configuration
// If the file didn't exist before, or if something has changed, re-save
if (buffer == null || !buffer.SequenceEqual(newBytes))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
Directory.CreateDirectory(Path.GetDirectoryName(path));
// Save it after load in case we got new items
File.WriteAllBytes(path, newBytes);
File.WriteAllBytes(path, newBytes);
}
return configuration;

View file

@ -3,13 +3,16 @@ using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Text;
using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Common.Implementations.Devices
{
public class DeviceId
{
private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly object _syncLock = new object();
@ -24,7 +27,7 @@ namespace MediaBrowser.Common.Implementations.Devices
{
lock (_syncLock)
{
var value = File.ReadAllText(CachePath, Encoding.UTF8);
var value = File.ReadAllText(CachePath, Encoding.UTF8);
Guid guid;
if (Guid.TryParse(value, out guid))
@ -55,11 +58,11 @@ namespace MediaBrowser.Common.Implementations.Devices
{
var path = CachePath;
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
lock (_syncLock)
{
File.WriteAllText(path, id, Encoding.UTF8);
_fileSystem.WriteAllText(path, id, Encoding.UTF8);
}
}
catch (Exception ex)
@ -88,10 +91,15 @@ namespace MediaBrowser.Common.Implementations.Devices
private string _id;
public DeviceId(IApplicationPaths appPaths, ILogger logger)
public DeviceId(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
{
if (fileSystem == null) {
throw new ArgumentNullException ("fileSystem");
}
_appPaths = appPaths;
_logger = logger;
_fileSystem = fileSystem;
}
public string Value

View file

@ -17,6 +17,7 @@ using System.Net.Cache;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Common.Implementations.HttpClientManager
{
@ -282,8 +283,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
var url = options.Url;
var urlHash = url.ToLower().GetMD5().ToString("N");
var semaphore = GetLock(url);
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false);
@ -292,6 +292,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
return response;
}
var semaphore = GetLock(url);
await semaphore.WaitAsync(options.CancellationToken).ConfigureAwait(false);
try
@ -355,7 +357,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private async Task CacheResponse(HttpResponseInfo response, string responseCachePath)
{
Directory.CreateDirectory(Path.GetDirectoryName(responseCachePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(responseCachePath));
using (var responseStream = response.Content)
{
@ -464,20 +466,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
catch (OperationCanceledException ex)
{
var exception = GetCancellationException(options.Url, options.CancellationToken, ex);
var httpException = exception as HttpException;
if (httpException != null && httpException.IsTimedOut)
{
client.LastTimeout = DateTime.UtcNow;
}
throw exception;
throw GetCancellationException(options, client, options.CancellationToken, ex);
}
catch (Exception ex)
{
throw GetException(ex, options);
throw GetException(ex, options, client);
}
finally
{
@ -488,27 +481,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
}
/// <summary>
/// Gets the exception.
/// </summary>
/// <param name="ex">The ex.</param>
/// <param name="options">The options.</param>
/// <returns>HttpException.</returns>
private HttpException GetException(WebException ex, HttpRequestOptions options)
{
_logger.ErrorException("Error getting response from " + options.Url, ex);
var exception = new HttpException(ex.Message, ex);
var response = ex.Response as HttpWebResponse;
if (response != null)
{
exception.StatusCode = response.StatusCode;
}
return exception;
}
private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable)
{
return new HttpResponseInfo(disposable)
@ -599,7 +571,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
{
ValidateParams(options);
Directory.CreateDirectory(_appPaths.TempDirectory);
_fileSystem.CreateDirectory(_appPaths.TempDirectory);
var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
@ -624,6 +596,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
_logger.Info("HttpClientManager.GetTempFileResponse url: {0}", options.Url);
}
var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression);
try
{
options.CancellationToken.ThrowIfCancellationRequested();
@ -632,7 +606,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
{
var httpResponse = (HttpWebResponse)response;
var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression);
EnsureSuccessStatusCode(client, httpResponse, options);
options.CancellationToken.ThrowIfCancellationRequested();
@ -669,7 +642,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
catch (Exception ex)
{
DeleteTempFile(tempFile);
throw GetException(ex, options);
throw GetException(ex, options, client);
}
finally
{
@ -694,14 +667,37 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private Exception GetException(Exception ex, HttpRequestOptions options)
private Exception GetException(Exception ex, HttpRequestOptions options, HttpClientInfo client)
{
if (ex is HttpException)
{
return ex;
}
var webException = ex as WebException
?? ex.InnerException as WebException;
if (webException != null)
{
return GetException(webException, options);
if (options.LogErrors)
{
_logger.ErrorException("Error getting response from " + options.Url, ex);
}
var exception = new HttpException(ex.Message, ex);
var response = webException.Response as HttpWebResponse;
if (response != null)
{
exception.StatusCode = response.StatusCode;
if ((int)response.StatusCode == 429)
{
client.LastTimeout = DateTime.UtcNow;
}
}
return exception;
}
var operationCanceledException = ex as OperationCanceledException
@ -709,10 +705,13 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
if (operationCanceledException != null)
{
return GetCancellationException(options.Url, options.CancellationToken, operationCanceledException);
return GetCancellationException(options, client, options.CancellationToken, operationCanceledException);
}
_logger.ErrorException("Error getting response from " + options.Url, ex);
if (options.LogErrors)
{
_logger.ErrorException("Error getting response from " + options.Url, ex);
}
return ex;
}
@ -784,18 +783,24 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// <summary>
/// Throws the cancellation exception.
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="options">The options.</param>
/// <param name="client">The client.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="exception">The exception.</param>
/// <returns>Exception.</returns>
private Exception GetCancellationException(string url, CancellationToken cancellationToken, OperationCanceledException exception)
private Exception GetCancellationException(HttpRequestOptions options, HttpClientInfo client, CancellationToken cancellationToken, OperationCanceledException exception)
{
// If the HttpClient's timeout is reached, it will cancel the Task internally
if (!cancellationToken.IsCancellationRequested)
{
var msg = string.Format("Connection to {0} timed out", url);
var msg = string.Format("Connection to {0} timed out", options.Url);
_logger.Error(msg);
if (options.LogErrors)
{
_logger.Error(msg);
}
client.LastTimeout = DateTime.UtcNow;
// Throw an HttpException so that the caller doesn't think it was cancelled by user code
return new HttpException(msg, exception)
@ -815,12 +820,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
if (!isSuccessful)
{
if ((int) statusCode == 429)
{
client.LastTimeout = DateTime.UtcNow;
}
if (statusCode == HttpStatusCode.RequestEntityTooLarge)
if (options.LogErrorResponseBody)
{
try
@ -869,25 +868,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
Task<WebResponse> asyncTask = Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null);
ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true);
asyncTask.ContinueWith(task =>
{
taskCompletion.TrySetResult(task.Result);
}, TaskContinuationOptions.NotOnFaulted);
var callback = new TaskCallback { taskCompletion = taskCompletion };
asyncTask.ContinueWith(callback.OnSuccess, TaskContinuationOptions.NotOnFaulted);
// Handle errors
asyncTask.ContinueWith(task =>
{
if (task.Exception != null)
{
taskCompletion.TrySetException(task.Exception);
}
else
{
taskCompletion.TrySetException(new List<Exception>());
}
}, TaskContinuationOptions.OnlyOnFaulted);
asyncTask.ContinueWith(callback.OnError, TaskContinuationOptions.OnlyOnFaulted);
return taskCompletion.Task;
}
@ -903,5 +888,27 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
}
}
private class TaskCallback
{
public TaskCompletionSource<WebResponse> taskCompletion;
public void OnSuccess(Task<WebResponse> task)
{
taskCompletion.TrySetResult(task.Result);
}
public void OnError(Task<WebResponse> task)
{
if (task.Exception != null)
{
taskCompletion.TrySetException(task.Exception);
}
else
{
taskCompletion.TrySetException(new List<Exception>());
}
}
}
}
}

View file

@ -1,433 +0,0 @@
using MediaBrowser.Model.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Text;
namespace MediaBrowser.Common.Implementations.IO
{
/// <summary>
/// Class CommonFileSystem
/// </summary>
public class CommonFileSystem : IFileSystem
{
protected ILogger Logger;
private readonly bool _supportsAsyncFileStreams;
private char[] _invalidFileNameChars;
public CommonFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool usePresetInvalidFileNameChars)
{
Logger = logger;
_supportsAsyncFileStreams = supportsAsyncFileStreams;
SetInvalidFileNameChars(usePresetInvalidFileNameChars);
}
private void SetInvalidFileNameChars(bool usePresetInvalidFileNameChars)
{
// GetInvalidFileNameChars is less restrictive in Linux/Mac than Windows, this mimic Windows behavior for mono under Linux/Mac.
if (usePresetInvalidFileNameChars)
{
_invalidFileNameChars = new char[41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
'\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
'\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
'\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
}
else
{
_invalidFileNameChars = Path.GetInvalidFileNameChars();
}
}
/// <summary>
/// Determines whether the specified filename is shortcut.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ArgumentNullException">filename</exception>
public virtual bool IsShortcut(string filename)
{
if (string.IsNullOrEmpty(filename))
{
throw new ArgumentNullException("filename");
}
var extension = Path.GetExtension(filename);
return string.Equals(extension, ".mblink", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Resolves the shortcut.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
/// <exception cref="System.ArgumentNullException">filename</exception>
public virtual string ResolveShortcut(string filename)
{
if (string.IsNullOrEmpty(filename))
{
throw new ArgumentNullException("filename");
}
if (string.Equals(Path.GetExtension(filename), ".mblink", StringComparison.OrdinalIgnoreCase))
{
var path = File.ReadAllText(filename);
return NormalizePath(path);
}
return null;
}
/// <summary>
/// Creates the shortcut.
/// </summary>
/// <param name="shortcutPath">The shortcut path.</param>
/// <param name="target">The target.</param>
/// <exception cref="System.ArgumentNullException">
/// shortcutPath
/// or
/// target
/// </exception>
public void CreateShortcut(string shortcutPath, string target)
{
if (string.IsNullOrEmpty(shortcutPath))
{
throw new ArgumentNullException("shortcutPath");
}
if (string.IsNullOrEmpty(target))
{
throw new ArgumentNullException("target");
}
File.WriteAllText(shortcutPath, target);
}
/// <summary>
/// Gets the file system info.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>FileSystemInfo.</returns>
public FileSystemInfo GetFileSystemInfo(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
// Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
if (Path.HasExtension(path))
{
var fileInfo = new FileInfo(path);
if (fileInfo.Exists)
{
return fileInfo;
}
return new DirectoryInfo(path);
}
else
{
var fileInfo = new DirectoryInfo(path);
if (fileInfo.Exists)
{
return fileInfo;
}
return new FileInfo(path);
}
}
/// <summary>
/// The space char
/// </summary>
private const char SpaceChar = ' ';
/// <summary>
/// Takes a filename and removes invalid characters
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
/// <exception cref="System.ArgumentNullException">filename</exception>
public string GetValidFilename(string filename)
{
if (string.IsNullOrEmpty(filename))
{
throw new ArgumentNullException("filename");
}
var builder = new StringBuilder(filename);
foreach (var c in _invalidFileNameChars)
{
builder = builder.Replace(c, SpaceChar);
}
return builder.ToString();
}
/// <summary>
/// Gets the creation time UTC.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>DateTime.</returns>
public DateTime GetCreationTimeUtc(FileSystemInfo info)
{
// This could throw an error on some file systems that have dates out of range
try
{
return info.CreationTimeUtc;
}
catch (Exception ex)
{
Logger.ErrorException("Error determining CreationTimeUtc for {0}", ex, info.FullName);
return DateTime.MinValue;
}
}
/// <summary>
/// Gets the creation time UTC.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>DateTime.</returns>
public DateTime GetLastWriteTimeUtc(FileSystemInfo info)
{
// This could throw an error on some file systems that have dates out of range
try
{
return info.LastWriteTimeUtc;
}
catch (Exception ex)
{
Logger.ErrorException("Error determining LastAccessTimeUtc for {0}", ex, info.FullName);
return DateTime.MinValue;
}
}
/// <summary>
/// Gets the last write time UTC.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>DateTime.</returns>
public DateTime GetLastWriteTimeUtc(string path)
{
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
}
/// <summary>
/// Gets the file stream.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="mode">The mode.</param>
/// <param name="access">The access.</param>
/// <param name="share">The share.</param>
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
/// <returns>FileStream.</returns>
public FileStream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share, bool isAsync = false)
{
if (_supportsAsyncFileStreams && isAsync)
{
return new FileStream(path, mode, access, share, StreamDefaults.DefaultFileStreamBufferSize, true);
}
return new FileStream(path, mode, access, share, StreamDefaults.DefaultFileStreamBufferSize);
}
/// <summary>
/// Swaps the files.
/// </summary>
/// <param name="file1">The file1.</param>
/// <param name="file2">The file2.</param>
public void SwapFiles(string file1, string file2)
{
if (string.IsNullOrEmpty(file1))
{
throw new ArgumentNullException("file1");
}
if (string.IsNullOrEmpty(file2))
{
throw new ArgumentNullException("file2");
}
var temp1 = Path.GetTempFileName();
var temp2 = Path.GetTempFileName();
// Copying over will fail against hidden files
RemoveHiddenAttribute(file1);
RemoveHiddenAttribute(file2);
File.Copy(file1, temp1, true);
File.Copy(file2, temp2, true);
File.Copy(temp1, file2, true);
File.Copy(temp2, file1, true);
DeleteFile(temp1);
DeleteFile(temp2);
}
/// <summary>
/// Removes the hidden attribute.
/// </summary>
/// <param name="path">The path.</param>
private void RemoveHiddenAttribute(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
var currentFile = new FileInfo(path);
// This will fail if the file is hidden
if (currentFile.Exists)
{
if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
{
currentFile.Attributes &= ~FileAttributes.Hidden;
}
}
}
public bool ContainsSubPath(string parentPath, string path)
{
if (string.IsNullOrEmpty(parentPath))
{
throw new ArgumentNullException("parentPath");
}
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
return path.IndexOf(parentPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) != -1;
}
public bool IsRootPath(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
var parent = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(parent))
{
return false;
}
return true;
}
public string NormalizePath(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
if (path.EndsWith(":\\", StringComparison.OrdinalIgnoreCase))
{
return path;
}
return path.TrimEnd(Path.DirectorySeparatorChar);
}
public string SubstitutePath(string path, string from, string to)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException("path");
}
if (string.IsNullOrWhiteSpace(from))
{
throw new ArgumentNullException("from");
}
if (string.IsNullOrWhiteSpace(to))
{
throw new ArgumentNullException("to");
}
var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
if (!string.Equals(newPath, path))
{
if (to.IndexOf('/') != -1)
{
newPath = newPath.Replace('\\', '/');
}
else
{
newPath = newPath.Replace('/', '\\');
}
}
return newPath;
}
public string GetFileNameWithoutExtension(FileSystemInfo info)
{
if (info is DirectoryInfo)
{
return info.Name;
}
return Path.GetFileNameWithoutExtension(info.FullName);
}
public string GetFileNameWithoutExtension(string path)
{
return Path.GetFileNameWithoutExtension(path);
}
public bool IsPathFile(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException("path");
}
// Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
!path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
return true;
//return Path.IsPathRooted(path);
}
public void DeleteFile(string path, bool sendToRecycleBin)
{
File.Delete(path);
}
public void DeleteDirectory(string path, bool recursive, bool sendToRecycleBin)
{
Directory.Delete(path, recursive);
}
public void DeleteFile(string path)
{
DeleteFile(path, false);
}
public void DeleteDirectory(string path, bool recursive)
{
DeleteDirectory(path, recursive, false);
}
}
}

View file

@ -170,7 +170,7 @@ namespace MediaBrowser.Common.Implementations.Logging
/// </summary>
/// <param name="name">The name.</param>
/// <returns>ILogger.</returns>
public ILogger GetLogger(string name)
public Model.Logging.ILogger GetLogger(string name)
{
return new NLogger(name, this);
}
@ -208,7 +208,7 @@ namespace MediaBrowser.Common.Implementations.Logging
{
LogFilePath = Path.Combine(LogDirectory, LogFilePrefix + "-" + decimal.Round(DateTime.Now.Ticks / 10000000) + ".txt");
Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));
Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));
AddFileTarget(LogFilePath, level);

View file

@ -14,7 +14,6 @@
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -48,18 +47,24 @@
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<Reference Include="NLog, Version=3.2.1.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<Reference Include="CommonIO, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NLog.3.2.1\lib\net45\NLog.dll</HintPath>
<HintPath>..\packages\CommonIO.1.0.0.5\lib\net45\CommonIO.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NLog.4.1.1\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="Patterns.Logging">
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
</Reference>
<Reference Include="SharpCompress, Version=0.10.2.0, Culture=neutral, PublicKeyToken=beaf6f427e128133, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
</Reference>
<Reference Include="SimpleInjector, Version=2.7.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
<Reference Include="SimpleInjector, Version=2.8.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SimpleInjector.2.8.0\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\SimpleInjector.3.0.5\lib\net45\SimpleInjector.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@ -82,7 +87,6 @@
<Compile Include="Devices\DeviceId.cs" />
<Compile Include="HttpClientManager\HttpClientInfo.cs" />
<Compile Include="HttpClientManager\HttpClientManager.cs" />
<Compile Include="IO\CommonFileSystem.cs" />
<Compile Include="IO\IsoManager.cs" />
<Compile Include="Logging\LogHelper.cs" />
<Compile Include="Logging\NLogger.cs" />

View file

@ -12,6 +12,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
@ -51,6 +52,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// </summary>
/// <value>The task manager.</value>
private ITaskManager TaskManager { get; set; }
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
@ -71,7 +73,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// or
/// logger
/// </exception>
public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger)
public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem)
{
if (scheduledTask == null)
{
@ -99,6 +101,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
TaskManager = taskManager;
JsonSerializer = jsonSerializer;
Logger = logger;
_fileSystem = fileSystem;
ReloadTriggerEvents(true);
}
@ -154,7 +157,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
_lastExecutionResult = value;
var path = GetHistoryFilePath();
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
lock (_lastExecutionResultSyncLock)
{
@ -300,6 +303,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
}
}
public void ReloadTriggerEvents()
{
ReloadTriggerEvents(false);
}
/// <summary>
/// Reloads the trigger events.
/// </summary>
@ -552,7 +560,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
var path = GetConfigurationFilePath();
Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
JsonSerializer.SerializeToFile(triggers.Select(ScheduledTaskHelpers.GetTriggerInfo), path);
}
@ -622,7 +630,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
try
{
Logger.Debug(Name + ": Cancelling");
Logger.Info(Name + ": Cancelling");
token.Cancel();
}
catch (Exception ex)
@ -635,16 +643,16 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
try
{
Logger.Debug(Name + ": Waiting on Task");
Logger.Info(Name + ": Waiting on Task");
var exited = Task.WaitAll(new[] { task }, 2000);
if (exited)
{
Logger.Debug(Name + ": Task exited");
Logger.Info(Name + ": Task exited");
}
else
{
Logger.Debug(Name + ": Timed out waiting for task to stop");
Logger.Info(Name + ": Timed out waiting for task to stop");
}
}
catch (Exception ex)

View file

@ -10,6 +10,9 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
using Microsoft.Win32;
namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
@ -50,6 +53,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// </summary>
/// <value>The logger.</value>
private ILogger Logger { get; set; }
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="TaskManager" /> class.
@ -58,13 +62,36 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentException">kernel</exception>
public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger)
public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem)
{
ApplicationPaths = applicationPaths;
JsonSerializer = jsonSerializer;
Logger = logger;
_fileSystem = fileSystem;
ScheduledTasks = new IScheduledTaskWorker[] { };
BindToSystemEvent();
}
private void BindToSystemEvent()
{
try
{
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}
catch
{
}
}
void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
foreach (var task in ScheduledTasks)
{
task.ReloadTriggerEvents();
}
}
/// <summary>
@ -106,9 +133,16 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
public void QueueScheduledTask<T>(TaskExecutionOptions options)
where T : IScheduledTask
{
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
QueueScheduledTask(scheduledTask, options);
if (scheduledTask == null)
{
Logger.Error("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
}
else
{
QueueScheduledTask(scheduledTask, options);
}
}
public void QueueScheduledTask<T>()
@ -116,7 +150,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
QueueScheduledTask<T>(new TaskExecutionOptions());
}
/// <summary>
/// Queues the scheduled task.
/// </summary>
@ -124,9 +158,16 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <param name="options">The task options.</param>
public void QueueScheduledTask(IScheduledTask task, TaskExecutionOptions options)
{
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == task.GetType());
var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == task.GetType());
QueueScheduledTask(scheduledTask, options);
if (scheduledTask == null)
{
Logger.Error("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
}
else
{
QueueScheduledTask(scheduledTask, options);
}
}
/// <summary>
@ -161,7 +202,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
var myTasks = ScheduledTasks.ToList();
var list = tasks.ToList();
myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger)));
myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)));
ScheduledTasks = myTasks.ToArray();
}

View file

@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
@ -95,7 +96,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <param name="progress">The progress.</param>
private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress)
{
var filesToDelete = new DirectoryInfo(directory).EnumerateFiles("*", SearchOption.AllDirectories)
var filesToDelete = _fileSystem.GetFiles(directory, true)
.Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
.ToList();
@ -120,14 +121,14 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
progress.Report(100);
}
private static void DeleteEmptyFolders(string parent)
private void DeleteEmptyFolders(string parent)
{
foreach (var directory in Directory.GetDirectories(parent))
foreach (var directory in _fileSystem.GetDirectoryPaths(parent))
{
DeleteEmptyFolders(directory);
if (!Directory.EnumerateFileSystemEntries(directory).Any())
if (!_fileSystem.GetFileSystemEntryPaths(directory).Any())
{
Directory.Delete(directory, false);
_fileSystem.DeleteDirectory(directory, false);
}
}
}

View file

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
@ -58,7 +59,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
// Delete log files more than n days old
var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays));
var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories)
var filesToDelete = _fileSystem.GetFiles(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, true)
.Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
.ToList();

View file

@ -99,7 +99,7 @@ namespace MediaBrowser.Common.Implementations.Security
{
try
{
contents = File.ReadAllLines(licenseFile);
contents = File.ReadAllLines(licenseFile);
}
catch (DirectoryNotFoundException)
{
@ -107,7 +107,7 @@ namespace MediaBrowser.Common.Implementations.Security
}
catch (FileNotFoundException)
{
(File.Create(licenseFile)).Close();
(File.Create(licenseFile)).Close();
}
}
if (contents != null && contents.Length > 0)
@ -150,8 +150,8 @@ namespace MediaBrowser.Common.Implementations.Security
}
var licenseFile = Filename;
Directory.CreateDirectory(Path.GetDirectoryName(licenseFile));
lock (_fileLock) File.WriteAllLines(licenseFile, lines);
Directory.CreateDirectory(Path.GetDirectoryName(licenseFile));
lock (_fileLock) File.WriteAllLines(licenseFile, lines);
}
}
}

View file

@ -1,4 +1,5 @@
using MediaBrowser.Common.Configuration;
using System.IO;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Security;
using MediaBrowser.Model.Entities;
@ -18,6 +19,7 @@ namespace MediaBrowser.Common.Implementations.Security
public class PluginSecurityManager : ISecurityManager
{
private const string MBValidateUrl = MbAdmin.HttpsUrl + "service/registration/validate";
private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "http://mb3admin.com/admin/" + "service/appstore/register";
/// <summary>
/// The _is MB supporter
@ -185,6 +187,70 @@ namespace MediaBrowser.Common.Implementations.Security
}
}
/// <summary>
/// Register an app store sale with our back-end. It will validate the transaction with the store
/// and then register the proper feature and then fill in the supporter key on success.
/// </summary>
/// <param name="parameters">Json parameters to send to admin server</param>
public async Task RegisterAppStoreSale(string parameters)
{
var options = new HttpRequestOptions()
{
Url = AppstoreRegUrl,
CancellationToken = CancellationToken.None
};
options.RequestHeaders.Add("X-Emby-Token", _appHost.SystemId);
options.RequestContent = parameters;
options.RequestContentType = "application/json";
try
{
using (var response = await _httpClient.Post(options).ConfigureAwait(false))
{
var reg = _jsonSerializer.DeserializeFromStream<RegRecord>(response.Content);
if (reg == null)
{
var msg = "Result from appstore registration was null.";
_logger.Error(msg);
throw new ApplicationException(msg);
}
if (!String.IsNullOrEmpty(reg.key))
{
SupporterKey = reg.key;
}
}
}
catch (ApplicationException)
{
SaveAppStoreInfo(parameters);
throw;
}
catch (Exception e)
{
_logger.ErrorException("Error registering appstore purchase {0}", e, parameters ?? "NO PARMS SENT");
SaveAppStoreInfo(parameters);
//TODO - could create a re-try routine on start-up if this file is there. For now we can handle manually.
throw new ApplicationException("Error registering store sale");
}
}
private void SaveAppStoreInfo(string info)
{
// Save all transaction information to a file
try
{
File.WriteAllText(Path.Combine(_appPaths.ProgramDataPath, "apptrans-error.txt"), info);
}
catch (IOException)
{
}
}
private async Task<MBRegistrationRecord> GetRegistrationStatusInternal(string feature,
string mb2Equivalent = null,
string version = null)

View file

@ -7,5 +7,6 @@ namespace MediaBrowser.Common.Implementations.Security
public string featId { get; set; }
public bool registered { get; set; }
public DateTime expDate { get; set; }
public string key { get; set; }
}
}

View file

@ -1,6 +1,8 @@
using MediaBrowser.Model.Serialization;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.Serialization;
using System;
using System.IO;
using CommonIO;
namespace MediaBrowser.Common.Implementations.Serialization
{
@ -9,8 +11,11 @@ namespace MediaBrowser.Common.Implementations.Serialization
/// </summary>
public class JsonSerializer : IJsonSerializer
{
public JsonSerializer()
private readonly IFileSystem _fileSystem;
public JsonSerializer(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
Configure();
}
@ -53,7 +58,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
throw new ArgumentNullException("file");
}
using (Stream stream = File.Open(file, FileMode.Create))
using (Stream stream = _fileSystem.GetFileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
{
SerializeToStream(obj, stream);
}

View file

@ -3,6 +3,8 @@ using System;
using System.Collections.Concurrent;
using System.IO;
using System.Xml;
using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Common.Implementations.Serialization
{
@ -11,6 +13,13 @@ namespace MediaBrowser.Common.Implementations.Serialization
/// </summary>
public class XmlSerializer : IXmlSerializer
{
private IFileSystem _fileSystem;
public XmlSerializer(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
// Need to cache these
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
private readonly ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer> _serializers =
@ -83,7 +92,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
/// <returns>System.Object.</returns>
public object DeserializeFromFile(Type type, string file)
{
using (var stream = File.OpenRead(file))
using (var stream = _fileSystem.OpenRead(file))
{
return DeserializeFromStream(type, stream);
}

View file

@ -19,6 +19,7 @@ using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Common.Implementations.Updates
{
@ -553,7 +554,7 @@ namespace MediaBrowser.Common.Implementations.Updates
if (packageChecksum != Guid.Empty) // support for legacy uploads for now
{
using (var crypto = new MD5CryptoServiceProvider())
using (var stream = new BufferedStream(File.OpenRead(tempFile), 100000))
using (var stream = new BufferedStream(_fileSystem.OpenRead(tempFile), 100000))
{
var check = Guid.Parse(BitConverter.ToString(crypto.ComputeHash(stream)).Replace("-", String.Empty));
if (check != packageChecksum)
@ -568,12 +569,12 @@ namespace MediaBrowser.Common.Implementations.Updates
// Success - move it to the real target
try
{
Directory.CreateDirectory(Path.GetDirectoryName(target));
File.Copy(tempFile, target, true);
_fileSystem.CreateDirectory(Path.GetDirectoryName(target));
_fileSystem.CopyFile(tempFile, target, true);
//If it is an archive - write out a version file so we know what it is
if (isArchive)
{
File.WriteAllText(target + ".ver", package.versionStr);
File.WriteAllText(target + ".ver", package.versionStr);
}
}
catch (IOException e)

View file

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NLog" version="3.2.1" targetFramework="net45" />
<package id="SimpleInjector" version="2.8.0" targetFramework="net45" />
<package id="CommonIO" version="1.0.0.5" targetFramework="net45" />
<package id="NLog" version="4.1.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.0.5" targetFramework="net45" />
</packages>

View file

@ -1,165 +0,0 @@
using System;
using System.IO;
namespace MediaBrowser.Common.IO
{
/// <summary>
/// Interface IFileSystem
/// </summary>
public interface IFileSystem
{
/// <summary>
/// Determines whether the specified filename is shortcut.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
bool IsShortcut(string filename);
/// <summary>
/// Resolves the shortcut.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
string ResolveShortcut(string filename);
/// <summary>
/// Creates the shortcut.
/// </summary>
/// <param name="shortcutPath">The shortcut path.</param>
/// <param name="target">The target.</param>
void CreateShortcut(string shortcutPath, string target);
/// <summary>
/// Gets the file system info.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>FileSystemInfo.</returns>
FileSystemInfo GetFileSystemInfo(string path);
/// <summary>
/// Gets the valid filename.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
string GetValidFilename(string filename);
/// <summary>
/// Gets the creation time UTC.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>DateTime.</returns>
DateTime GetCreationTimeUtc(FileSystemInfo info);
/// <summary>
/// Gets the last write time UTC.
/// </summary>
/// <param name="info">The information.</param>
/// <returns>DateTime.</returns>
DateTime GetLastWriteTimeUtc(FileSystemInfo info);
/// <summary>
/// Gets the last write time UTC.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>DateTime.</returns>
DateTime GetLastWriteTimeUtc(string path);
/// <summary>
/// Gets the file stream.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="mode">The mode.</param>
/// <param name="access">The access.</param>
/// <param name="share">The share.</param>
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
/// <returns>FileStream.</returns>
FileStream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share, bool isAsync = false);
/// <summary>
/// Swaps the files.
/// </summary>
/// <param name="file1">The file1.</param>
/// <param name="file2">The file2.</param>
void SwapFiles(string file1, string file2);
/// <summary>
/// Determines whether [contains sub path] [the specified parent path].
/// </summary>
/// <param name="parentPath">The parent path.</param>
/// <param name="path">The path.</param>
/// <returns><c>true</c> if [contains sub path] [the specified parent path]; otherwise, <c>false</c>.</returns>
bool ContainsSubPath(string parentPath, string path);
/// <summary>
/// Determines whether [is root path] [the specified path].
/// </summary>
/// <param name="path">The path.</param>
/// <returns><c>true</c> if [is root path] [the specified path]; otherwise, <c>false</c>.</returns>
bool IsRootPath(string path);
/// <summary>
/// Normalizes the path.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.String.</returns>
string NormalizePath(string path);
/// <summary>
/// Substitutes the path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="from">From.</param>
/// <param name="to">To.</param>
/// <returns>System.String.</returns>
string SubstitutePath(string path, string from, string to);
/// <summary>
/// Gets the file name without extension.
/// </summary>
/// <param name="info">The information.</param>
/// <returns>System.String.</returns>
string GetFileNameWithoutExtension(FileSystemInfo info);
/// <summary>
/// Gets the file name without extension.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.String.</returns>
string GetFileNameWithoutExtension(string path);
/// <summary>
/// Determines whether [is path file] [the specified path].
/// </summary>
/// <param name="path">The path.</param>
/// <returns><c>true</c> if [is path file] [the specified path]; otherwise, <c>false</c>.</returns>
bool IsPathFile(string path);
/// <summary>
/// Deletes the file.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="sendToRecycleBin">if set to <c>true</c> [send to recycle bin].</param>
void DeleteFile(string path, bool sendToRecycleBin);
/// <summary>
/// Deletes the directory.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="recursive">if set to <c>true</c> [recursive].</param>
/// <param name="sendToRecycleBin">if set to <c>true</c> [send to recycle bin].</param>
void DeleteDirectory(string path, bool recursive, bool sendToRecycleBin);
/// <summary>
/// Deletes the file.
/// </summary>
/// <param name="path">The path.</param>
void DeleteFile(string path);
/// <summary>
/// Deletes the directory.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="recursive">if set to <c>true</c> [recursive].</param>
void DeleteDirectory(string path, bool recursive);
}
}

View file

@ -60,7 +60,6 @@
<Compile Include="Extensions\BaseExtensions.cs" />
<Compile Include="Extensions\ResourceNotFoundException.cs" />
<Compile Include="IDependencyContainer.cs" />
<Compile Include="IO\IFileSystem.cs" />
<Compile Include="IO\ProgressStream.cs" />
<Compile Include="IO\StreamDefaults.cs" />
<Compile Include="Configuration\IApplicationPaths.cs" />

View file

@ -87,6 +87,7 @@ namespace MediaBrowser.Common.Net
public bool BufferContent { get; set; }
public bool LogRequest { get; set; }
public bool LogErrors { get; set; }
public bool LogErrorResponseBody { get; set; }
public bool EnableKeepAlive { get; set; }
@ -116,6 +117,7 @@ namespace MediaBrowser.Common.Net
RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
LogRequest = true;
LogErrors = true;
CacheMode = CacheMode.None;
TimeoutMs = 20000;

View file

@ -69,5 +69,10 @@ namespace MediaBrowser.Common.ScheduledTasks
/// </summary>
/// <value>The unique id.</value>
string Id { get; }
/// <summary>
/// Reloads the trigger events.
/// </summary>
void ReloadTriggerEvents();
}
}

View file

@ -51,8 +51,8 @@ namespace MediaBrowser.Common.ScheduledTasks
DisposeTimer();
var triggerDate = lastResult != null ?
lastResult.EndTimeUtc.Add(Interval) :
DateTime.UtcNow.Add(FirstRunDelay);
lastResult.EndTimeUtc.Add(Interval) :
DateTime.UtcNow.Add(FirstRunDelay);
if (DateTime.UtcNow > triggerDate)
{
@ -62,7 +62,7 @@ namespace MediaBrowser.Common.ScheduledTasks
}
else
{
triggerDate = DateTime.UtcNow.Add(Interval);
triggerDate = DateTime.UtcNow.AddMinutes(1);
}
}

View file

@ -1,3 +1,4 @@
using System;
using MediaBrowser.Model.Entities;
using System.Threading.Tasks;
@ -45,5 +46,11 @@ namespace MediaBrowser.Common.Security
/// </summary>
/// <returns>Task&lt;SupporterInfo&gt;.</returns>
Task<SupporterInfo> GetSupporterInfo();
/// <summary>
/// Register and app store sale with our back-end
/// </summary>
/// <param name="parameters">Json parameters to pass to admin server</param>
Task RegisterAppStoreSale(string parameters);
}
}

View file

@ -10,8 +10,6 @@ namespace MediaBrowser.Controller.Channels
{
public class Channel : Folder
{
public string OriginalChannelName { get; set; }
public override bool IsVisible(User user)
{
if (user.Policy.BlockedChannels != null)

View file

@ -7,24 +7,15 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
namespace MediaBrowser.Controller.Channels
{
public class ChannelAudioItem : Audio, IChannelMediaItem
{
public string ExternalId { get; set; }
public string DataVersion { get; set; }
public ChannelItemType ChannelItemType { get; set; }
public bool IsInfiniteStream { get; set; }
public ChannelMediaContentType ContentType { get; set; }
public string OriginalImageUrl { get; set; }
public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
protected override bool GetBlockUnratedValue(UserPolicy config)
@ -37,6 +28,7 @@ namespace MediaBrowser.Controller.Channels
return ExternalId;
}
[IgnoreDataMember]
public override bool SupportsLocalMetadata
{
get
@ -55,6 +47,7 @@ namespace MediaBrowser.Controller.Channels
ChannelMediaSources = new List<ChannelMediaInfo>();
}
[IgnoreDataMember]
public override LocationType LocationType
{
get

View file

@ -3,28 +3,24 @@ using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Users;
using System;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Channels
{
public class ChannelFolderItem : Folder, IChannelItem
{
public string ExternalId { get; set; }
public string DataVersion { get; set; }
public ChannelItemType ChannelItemType { get; set; }
public ChannelFolderType ChannelFolderType { get; set; }
public string OriginalImageUrl { get; set; }
protected override bool GetBlockUnratedValue(UserPolicy config)
{
// Don't block.
return false;
}
[IgnoreDataMember]
public override bool SupportsLocalMetadata
{
get

View file

@ -8,24 +8,15 @@ using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
namespace MediaBrowser.Controller.Channels
{
public class ChannelVideoItem : Video, IChannelMediaItem, IHasLookupInfo<ChannelItemLookupInfo>
{
public string ExternalId { get; set; }
public string DataVersion { get; set; }
public ChannelItemType ChannelItemType { get; set; }
public bool IsInfiniteStream { get; set; }
public ChannelMediaContentType ContentType { get; set; }
public string OriginalImageUrl { get; set; }
public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
protected override string CreateUserDataKey()
@ -56,6 +47,7 @@ namespace MediaBrowser.Controller.Channels
return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
}
[IgnoreDataMember]
public override bool SupportsLocalMetadata
{
get
@ -74,6 +66,7 @@ namespace MediaBrowser.Controller.Channels
ChannelMediaSources = new List<ChannelMediaInfo>();
}
[IgnoreDataMember]
public override LocationType LocationType
{
get
@ -115,7 +108,11 @@ namespace MediaBrowser.Controller.Channels
var info = GetItemLookupInfo<ChannelItemLookupInfo>();
info.ContentType = ContentType;
info.ExtraType = ExtraType;
if (ExtraType.HasValue)
{
info.ExtraType = ExtraType.Value;
}
return info;
}

View file

@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace MediaBrowser.Controller.Channels
{
public interface IChannelFactory
{
IEnumerable<IChannel> GetChannels();
}
public interface IFactoryChannel
{
}
}

View file

@ -7,11 +7,5 @@ namespace MediaBrowser.Controller.Channels
string ChannelId { get; set; }
string ExternalId { get; set; }
ChannelItemType ChannelItemType { get; set; }
string OriginalImageUrl { get; set; }
string DataVersion { get; set; }
}
}

View file

@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.Channels
/// </summary>
/// <param name="channels">The channels.</param>
/// <param name="factories">The factories.</param>
void AddParts(IEnumerable<IChannel> channels, IEnumerable<IChannelFactory> factories);
void AddParts(IEnumerable<IChannel> channels);
/// <summary>
/// Gets the channel download path.

View file

@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Channels
ChannelMediaContentType ContentType { get; set; }
ExtraType ExtraType { get; set; }
ExtraType? ExtraType { get; set; }
List<ChannelMediaInfo> ChannelMediaSources { get; set; }
}

View file

@ -25,13 +25,6 @@ namespace MediaBrowser.Controller.Drawing
/// <value>The image enhancers.</value>
IEnumerable<IImageEnhancer> ImageEnhancers { get; }
/// <summary>
/// Gets the size of the image.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>ImageSize.</returns>
ImageSize GetImageSize(string path);
/// <summary>
/// Gets the size of the image.
/// </summary>
@ -39,6 +32,13 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>ImageSize.</returns>
ImageSize GetImageSize(ItemImageInfo info);
/// <summary>
/// Gets the size of the image.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>ImageSize.</returns>
ImageSize GetImageSize(string path);
/// <summary>
/// Adds the parts.
/// </summary>
@ -105,5 +105,11 @@ namespace MediaBrowser.Controller.Drawing
/// </summary>
/// <param name="options">The options.</param>
Task CreateImageCollage(ImageCollageOptions options);
/// <summary>
/// Gets a value indicating whether [supports image collage creation].
/// </summary>
/// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value>
bool SupportsImageCollageCreation { get; }
}
}

View file

@ -6,6 +6,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using CommonIO;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Controller.Entities
@ -62,7 +64,7 @@ namespace MediaBrowser.Controller.Entities
public List<string> PhysicalLocationsList { get; set; }
protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService)
protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
{
return CreateResolveArgs(directoryService).FileSystemChildren;
}
@ -73,7 +75,7 @@ namespace MediaBrowser.Controller.Entities
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths , directoryService)
{
FileInfo = new DirectoryInfo(path),
FileInfo = FileSystem.GetDirectoryInfo(path),
Path = path,
Parent = Parent
};
@ -94,7 +96,7 @@ namespace MediaBrowser.Controller.Entities
{
var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
fileSystemDictionary = paths.Select(FileSystem.GetDirectoryInfo).ToDictionary(i => i.FullName);
}
args.FileSystemDictionary = fileSystemDictionary;

View file

@ -24,14 +24,20 @@ namespace MediaBrowser.Controller.Entities.Audio
IThemeMedia,
IArchivable
{
public string FormatName { get; set; }
public long? Size { get; set; }
public string Container { get; set; }
public int? TotalBitrate { get; set; }
public List<string> Tags { get; set; }
public ExtraType ExtraType { get; set; }
public ExtraType? ExtraType { get; set; }
public bool IsThemeMedia { get; set; }
[IgnoreDataMember]
public bool IsThemeMedia
{
get
{
return ExtraType.HasValue && ExtraType.Value == Model.Entities.ExtraType.ThemeSong;
}
}
public Audio()
{
@ -46,12 +52,6 @@ namespace MediaBrowser.Controller.Entities.Audio
get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; }
}
/// <summary>
/// Gets or sets a value indicating whether this instance has embedded image.
/// </summary>
/// <value><c>true</c> if this instance has embedded image; otherwise, <c>false</c>.</value>
public bool HasEmbeddedImage { get; set; }
[IgnoreDataMember]
protected override bool SupportsOwnedItems
{
@ -212,8 +212,7 @@ namespace MediaBrowser.Controller.Entities.Audio
Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path,
RunTimeTicks = i.RunTimeTicks,
Container = i.Container,
Size = i.Size,
Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList()
Size = i.Size
};
if (string.IsNullOrEmpty(info.Container))

View file

@ -23,6 +23,7 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Controller.Entities
{
@ -38,7 +39,6 @@ namespace MediaBrowser.Controller.Entities
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
LockedFields = new List<MetadataFields>();
ImageInfos = new List<ItemImageInfo>();
Identities = new List<IItemIdentity>();
}
/// <summary>
@ -56,12 +56,16 @@ namespace MediaBrowser.Controller.Entities
public static string ThemeSongFilename = "theme";
public static string ThemeVideosFolderName = "backdrops";
public string PreferredMetadataCountryCode { get; set; }
public string PreferredMetadataLanguage { get; set; }
public List<ItemImageInfo> ImageInfos { get; set; }
/// <summary>
/// Gets or sets the channel identifier.
/// </summary>
/// <value>The channel identifier.</value>
[IgnoreDataMember]
public string ChannelId { get; set; }
[IgnoreDataMember]
@ -120,6 +124,12 @@ namespace MediaBrowser.Controller.Entities
/// <value>The id.</value>
public Guid Id { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is hd.
/// </summary>
/// <value><c>true</c> if this instance is hd; otherwise, <c>false</c>.</value>
public bool? IsHD { get; set; }
/// <summary>
/// Return the id that should be used to key display prefs for this item.
/// Default is based on the type for everything except actual generic folders.
@ -162,6 +172,26 @@ namespace MediaBrowser.Controller.Entities
}
}
/// <summary>
/// Id of the program.
/// </summary>
[IgnoreDataMember]
public string ExternalId
{
get { return this.GetProviderId("ProviderExternalId"); }
set
{
this.SetProviderId("ProviderExternalId", value);
}
}
/// <summary>
/// Gets or sets the etag.
/// </summary>
/// <value>The etag.</value>
[IgnoreDataMember]
public string ExternalEtag { get; set; }
[IgnoreDataMember]
public virtual bool IsHidden
{
@ -183,7 +213,7 @@ namespace MediaBrowser.Controller.Entities
{
// Local trailer, special feature, theme video, etc.
// An item that belongs to another item but is not part of the Parent-Child tree
return !IsFolder && Parent == null && LocationType == LocationType.FileSystem;
return !IsFolder && ParentId == Guid.Empty && LocationType == LocationType.FileSystem;
}
}
@ -305,6 +335,9 @@ namespace MediaBrowser.Controller.Entities
public DateTime DateLastSaved { get; set; }
[IgnoreDataMember]
public DateTime DateLastRefreshed { get; set; }
/// <summary>
/// The logger
/// </summary>
@ -331,30 +364,8 @@ namespace MediaBrowser.Controller.Entities
return Name;
}
/// <summary>
/// Returns true if this item should not attempt to fetch metadata
/// </summary>
/// <value><c>true</c> if [dont fetch meta]; otherwise, <c>false</c>.</value>
[Obsolete("Please use IsLocked instead of DontFetchMeta")]
public bool DontFetchMeta { get; set; }
[IgnoreDataMember]
public bool IsLocked
{
get
{
return DontFetchMeta;
}
set
{
DontFetchMeta = value;
}
}
public bool IsUnidentified { get; set; }
[IgnoreDataMember]
public List<IItemIdentity> Identities { get; set; }
public bool IsLocked { get; set; }
/// <summary>
/// Gets or sets the locked fields.
@ -484,7 +495,6 @@ namespace MediaBrowser.Controller.Entities
public Guid ParentId { get; set; }
private Folder _parent;
/// <summary>
/// Gets or sets the parent.
/// </summary>
@ -494,11 +504,6 @@ namespace MediaBrowser.Controller.Entities
{
get
{
if (_parent != null)
{
return _parent;
}
if (ParentId != Guid.Empty)
{
return LibraryManager.GetItemById(ParentId) as Folder;
@ -506,12 +511,14 @@ namespace MediaBrowser.Controller.Entities
return null;
}
set { _parent = value; }
set
{
}
}
public void SetParent(Folder parent)
{
Parent = parent;
ParentId = parent == null ? Guid.Empty : parent.Id;
}
@ -558,6 +565,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the end date.
/// </summary>
/// <value>The end date.</value>
[IgnoreDataMember]
public DateTime? EndDate { get; set; }
/// <summary>
@ -582,6 +590,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the custom rating.
/// </summary>
/// <value>The custom rating.</value>
//[IgnoreDataMember]
public string CustomRating { get; set; }
/// <summary>
@ -590,12 +599,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The overview.</value>
public string Overview { get; set; }
/// <summary>
/// Gets or sets the people.
/// </summary>
/// <value>The people.</value>
public List<PersonInfo> People { get; set; }
/// <summary>
/// Gets or sets the studios.
/// </summary>
@ -618,6 +621,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the community rating.
/// </summary>
/// <value>The community rating.</value>
//[IgnoreDataMember]
public float? CommunityRating { get; set; }
/// <summary>
@ -643,6 +647,7 @@ namespace MediaBrowser.Controller.Entities
/// This could be episode number, album track number, etc.
/// </summary>
/// <value>The index number.</value>
//[IgnoreDataMember]
public int? IndexNumber { get; set; }
/// <summary>
@ -662,7 +667,7 @@ namespace MediaBrowser.Controller.Entities
{
get
{
if (!string.IsNullOrEmpty(CustomRating))
if (!string.IsNullOrWhiteSpace(CustomRating))
{
return CustomRating;
}
@ -701,16 +706,16 @@ namespace MediaBrowser.Controller.Entities
/// Loads the theme songs.
/// </summary>
/// <returns>List{Audio.Audio}.</returns>
private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService)
private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var files = fileSystemChildren.OfType<DirectoryInfo>()
var files = fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
.SelectMany(i => directoryService.GetFiles(i.FullName))
.ToList();
// Support plex/xbmc convention
files.AddRange(fileSystemChildren.OfType<FileInfo>()
.Where(i => string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
files.AddRange(fileSystemChildren
.Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
);
return LibraryManager.ResolvePaths(files, directoryService, null)
@ -737,11 +742,11 @@ namespace MediaBrowser.Controller.Entities
/// Loads the video backdrops.
/// </summary>
/// <returns>List{Video}.</returns>
private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService)
private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var files = fileSystemChildren.OfType<DirectoryInfo>()
var files = fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
.SelectMany(i => directoryService.GetFiles(i.FullName));
return LibraryManager.ResolvePaths(files, directoryService, null)
.OfType<Video>()
@ -765,7 +770,7 @@ namespace MediaBrowser.Controller.Entities
public Task RefreshMetadata(CancellationToken cancellationToken)
{
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService()), cancellationToken);
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken);
}
/// <summary>
@ -786,7 +791,7 @@ namespace MediaBrowser.Controller.Entities
{
var files = locationType != LocationType.Remote && locationType != LocationType.Virtual ?
GetFileSystemChildren(options.DirectoryService).ToList() :
new List<FileSystemInfo>();
new List<FileSystemMetadata>();
var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false);
@ -833,7 +838,7 @@ namespace MediaBrowser.Controller.Entities
/// <param name="fileSystemChildren"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var themeSongsChanged = false;
@ -864,14 +869,14 @@ namespace MediaBrowser.Controller.Entities
return themeSongsChanged || themeVideosChanged || localTrailersChanged;
}
protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService)
protected virtual IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
{
var path = ContainingFolderPath;
return directoryService.GetFileSystemEntries(path);
}
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var newItems = LibraryManager.FindTrailers(this, fileSystemChildren, options.DirectoryService).ToList();
@ -888,7 +893,7 @@ namespace MediaBrowser.Controller.Entities
return itemsChanged;
}
private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var newThemeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService).ToList();
@ -902,7 +907,7 @@ namespace MediaBrowser.Controller.Entities
if (!i.IsThemeMedia)
{
i.IsThemeMedia = true;
i.ExtraType = ExtraType.ThemeVideo;
subOptions.ForceSave = true;
}
@ -919,7 +924,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Refreshes the theme songs.
/// </summary>
private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var newThemeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService).ToList();
var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList();
@ -932,7 +937,7 @@ namespace MediaBrowser.Controller.Entities
if (!i.IsThemeMedia)
{
i.IsThemeMedia = true;
i.ExtraType = ExtraType.ThemeSong;
subOptions.ForceSave = true;
}
@ -999,18 +1004,11 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
public string GetPreferredMetadataLanguage()
{
string lang = null;
var hasLang = this as IHasPreferredMetadataLanguage;
if (hasLang != null)
{
lang = hasLang.PreferredMetadataLanguage;
}
string lang = PreferredMetadataLanguage;
if (string.IsNullOrWhiteSpace(lang))
{
lang = Parents.OfType<IHasPreferredMetadataLanguage>()
lang = Parents
.Select(i => i.PreferredMetadataLanguage)
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
}
@ -1036,18 +1034,11 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
public string GetPreferredMetadataCountryCode()
{
string lang = null;
var hasLang = this as IHasPreferredMetadataLanguage;
if (hasLang != null)
{
lang = hasLang.PreferredMetadataCountryCode;
}
string lang = PreferredMetadataCountryCode;
if (string.IsNullOrWhiteSpace(lang))
{
lang = Parents.OfType<IHasPreferredMetadataLanguage>()
lang = Parents
.Select(i => i.PreferredMetadataCountryCode)
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
}
@ -1114,7 +1105,14 @@ namespace MediaBrowser.Controller.Entities
// Could not determine the integer value
if (!value.HasValue)
{
return true;
var isAllowed = !GetBlockUnratedValue(user.Policy);
if (!isAllowed)
{
Logger.Debug("{0} has an unrecognized parental rating of {1}.", Name, rating);
}
return isAllowed;
}
return value.Value <= maxAllowedRating.Value;
@ -1165,6 +1163,17 @@ namespace MediaBrowser.Controller.Entities
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
protected virtual bool GetBlockUnratedValue(UserPolicy config)
{
// Don't block plain folders that are unrated. Let the media underneath get blocked
// Special folders like series and albums will override this method.
if (IsFolder)
{
return false;
}
if (this is IItemByName)
{
return false;
}
return config.BlockUnratedItems.Contains(UnratedItem.Other);
}
@ -1418,7 +1427,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns>
public virtual Task ChangedExternally()
{
ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions());
ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions(FileSystem));
return Task.FromResult(true);
}
@ -1434,7 +1443,24 @@ namespace MediaBrowser.Controller.Entities
return GetImageInfo(type, imageIndex) != null;
}
public void SetImagePath(ImageType type, int index, FileSystemInfo file)
public void SetImage(ItemImageInfo image, int index)
{
if (image.Type == ImageType.Chapter)
{
throw new ArgumentException("Cannot set chapter images using SetImagePath");
}
var existingImage = GetImageInfo(image.Type, index);
if (existingImage != null)
{
ImageInfos.Remove(existingImage);
}
ImageInfos.Add(image);
}
public void SetImagePath(ImageType type, int index, FileSystemMetadata file)
{
if (type == ImageType.Chapter)
{
@ -1475,18 +1501,21 @@ namespace MediaBrowser.Controller.Entities
// Remove it from the item
RemoveImage(info);
// Delete the source file
var currentFile = new FileInfo(info.Path);
// Deletion will fail if the file is hidden so remove the attribute first
if (currentFile.Exists)
if (info.IsLocalFile)
{
if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
{
currentFile.Attributes &= ~FileAttributes.Hidden;
}
// Delete the source file
var currentFile = new FileInfo(info.Path);
FileSystem.DeleteFile(currentFile.FullName);
// Deletion will fail if the file is hidden so remove the attribute first
if (currentFile.Exists)
{
if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
{
currentFile.Attributes &= ~FileAttributes.Hidden;
}
FileSystem.DeleteFile(currentFile.FullName);
}
}
return UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
@ -1507,11 +1536,16 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public bool ValidateImages(IDirectoryService directoryService)
{
var allDirectories = ImageInfos.Select(i => System.IO.Path.GetDirectoryName(i.Path)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
var allFiles = allDirectories.SelectMany(directoryService.GetFiles).Select(i => i.FullName).ToList();
var allFiles = ImageInfos
.Where(i => i.IsLocalFile)
.Select(i => System.IO.Path.GetDirectoryName(i.Path))
.Distinct(StringComparer.OrdinalIgnoreCase)
.SelectMany(directoryService.GetFiles)
.Select(i => i.FullName)
.ToList();
var deletedImages = ImageInfos
.Where(image => !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase))
.Where(image => image.IsLocalFile && !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase))
.ToList();
if (deletedImages.Count > 0)
@ -1584,11 +1618,6 @@ namespace MediaBrowser.Controller.Entities
return ImageInfos.Where(i => i.Type == imageType);
}
public bool AddImages(ImageType imageType, IEnumerable<FileInfo> images)
{
return AddImages(imageType, images.Cast<FileSystemInfo>().ToList());
}
/// <summary>
/// Adds the images.
/// </summary>
@ -1596,7 +1625,7 @@ namespace MediaBrowser.Controller.Entities
/// <param name="images">The images.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
/// <exception cref="System.ArgumentException">Cannot call AddImages with chapter images</exception>
public bool AddImages(ImageType imageType, List<FileSystemInfo> images)
public bool AddImages(ImageType imageType, List<FileSystemMetadata> images)
{
if (imageType == ImageType.Chapter)
{
@ -1606,7 +1635,7 @@ namespace MediaBrowser.Controller.Entities
var existingImages = GetImages(imageType)
.ToList();
var newImageList = new List<FileSystemInfo>();
var newImageList = new List<FileSystemMetadata>();
var imageAdded = false;
foreach (var newImage in images)
@ -1626,7 +1655,10 @@ namespace MediaBrowser.Controller.Entities
}
else
{
existing.DateModified = FileSystem.GetLastWriteTimeUtc(newImage);
if (existing.IsLocalFile)
{
existing.DateModified = FileSystem.GetLastWriteTimeUtc(newImage);
}
}
}
@ -1635,7 +1667,7 @@ namespace MediaBrowser.Controller.Entities
var newImagePaths = images.Select(i => i.FullName).ToList();
var deleted = existingImages
.Where(i => !newImagePaths.Contains(i.Path, StringComparer.OrdinalIgnoreCase) && !File.Exists(i.Path))
.Where(i => i.IsLocalFile && !newImagePaths.Contains(i.Path, StringComparer.OrdinalIgnoreCase) && !FileSystem.FileExists(i.Path))
.ToList();
ImageInfos = ImageInfos.Except(deleted).ToList();
@ -1646,7 +1678,7 @@ namespace MediaBrowser.Controller.Entities
return newImageList.Count > 0;
}
private ItemImageInfo GetImageInfo(FileSystemInfo file, ImageType type)
private ItemImageInfo GetImageInfo(FileSystemMetadata file, ImageType type)
{
return new ItemImageInfo
{
@ -1686,6 +1718,12 @@ namespace MediaBrowser.Controller.Entities
return Task.FromResult(true);
}
if (!info1.IsLocalFile || !info2.IsLocalFile)
{
// TODO: Not supported yet
return Task.FromResult(true);
}
var path1 = info1.Path;
var path2 = info2.Path;
@ -1769,7 +1807,7 @@ namespace MediaBrowser.Controller.Entities
{
foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
{
path = FileSystem.SubstitutePath(path, map.From, map.To);
path = LibraryManager.SubstitutePath(path, map.From, map.To);
}
}
@ -1810,7 +1848,7 @@ namespace MediaBrowser.Controller.Entities
if (video == null)
{
video = LibraryManager.ResolvePath(new FileInfo(path)) as Video;
video = LibraryManager.ResolvePath(FileSystem.GetFileSystemInfo(path)) as Video;
newOptions.ForceSave = true;
}

View file

@ -7,7 +7,7 @@ using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
{
public class Book : BaseItem, IHasTags, IHasPreferredMetadataLanguage, IHasLookupInfo<BookInfo>, IHasSeries
public class Book : BaseItem, IHasTags, IHasLookupInfo<BookInfo>, IHasSeries
{
public override string MediaType
{
@ -25,14 +25,6 @@ namespace MediaBrowser.Controller.Entities
public string SeriesName { get; set; }
public string PreferredMetadataLanguage { get; set; }
/// <summary>
/// Gets or sets the preferred metadata country code.
/// </summary>
/// <value>The preferred metadata country code.</value>
public string PreferredMetadataCountryCode { get; set; }
public Book()
{
Tags = new List<string>();

View file

@ -8,6 +8,8 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Controller.Entities
{
@ -80,7 +82,7 @@ namespace MediaBrowser.Controller.Entities
public List<string> PhysicalLocationsList { get; set; }
protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService)
protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
{
return CreateResolveArgs(directoryService).FileSystemChildren;
}
@ -107,7 +109,7 @@ namespace MediaBrowser.Controller.Entities
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
{
FileInfo = new DirectoryInfo(path),
FileInfo = FileSystem.GetDirectoryInfo(path),
Path = path,
Parent = Parent,
CollectionType = CollectionType
@ -129,7 +131,7 @@ namespace MediaBrowser.Controller.Entities
{
var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
fileSystemDictionary = paths.Select(FileSystem.GetDirectoryInfo).ToDictionary(i => i.FullName);
}
args.FileSystemDictionary = fileSystemDictionary;

View file

@ -13,13 +13,15 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// Class Folder
/// </summary>
public class Folder : BaseItem, IHasThemeMedia, IHasTags, IHasPreferredMetadataLanguage
public class Folder : BaseItem, IHasThemeMedia, IHasTags
{
public static IUserManager UserManager { get; set; }
public static IUserViewManager UserViewManager { get; set; }
@ -28,14 +30,6 @@ namespace MediaBrowser.Controller.Entities
public List<Guid> ThemeVideoIds { get; set; }
public List<string> Tags { get; set; }
public string PreferredMetadataLanguage { get; set; }
/// <summary>
/// Gets or sets the preferred metadata country code.
/// </summary>
/// <value>The preferred metadata country code.</value>
public string PreferredMetadataCountryCode { get; set; }
public Folder()
{
LinkedChildren = new List<LinkedChild>();
@ -48,7 +42,7 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public virtual bool IsPreSorted
{
get { return ConfigurationManager.Configuration.EnableWindowsShortcuts; }
get { return false; }
}
/// <summary>
@ -120,7 +114,7 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
protected virtual bool SupportsShortcutChildren
{
get { return false; }
get { return ConfigurationManager.Configuration.EnableWindowsShortcuts; }
}
/// <summary>
@ -213,7 +207,7 @@ namespace MediaBrowser.Controller.Entities
return base.OfficialRatingForComparison;
}
return !string.IsNullOrEmpty(base.OfficialRatingForComparison) ? base.OfficialRatingForComparison : "None";
return !string.IsNullOrWhiteSpace(base.OfficialRatingForComparison) ? base.OfficialRatingForComparison : "None";
}
}
@ -320,7 +314,7 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public IEnumerable<BaseItem> Children
{
get { return ActualChildren; }
get { return ActualChildren.ToList(); }
}
/// <summary>
@ -371,7 +365,7 @@ namespace MediaBrowser.Controller.Entities
public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken)
{
return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService()));
return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(FileSystem)));
}
/// <summary>
@ -474,7 +468,7 @@ namespace MediaBrowser.Controller.Entities
currentChild.DateModified = child.DateModified;
}
currentChild.IsOffline = false;
await UpdateIsOffline(currentChild, false).ConfigureAwait(false);
validChildren.Add(currentChild);
}
else
@ -509,12 +503,12 @@ namespace MediaBrowser.Controller.Entities
else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
{
item.IsOffline = true;
await UpdateIsOffline(item, true).ConfigureAwait(false);
validChildren.Add(item);
}
else
{
item.IsOffline = false;
await UpdateIsOffline(item, false).ConfigureAwait(false);
actualRemovals.Add(item);
}
}
@ -569,6 +563,17 @@ namespace MediaBrowser.Controller.Entities
progress.Report(100);
}
private Task UpdateIsOffline(BaseItem item, bool newValue)
{
if (item.IsOffline != newValue)
{
item.IsOffline = newValue;
return item.UpdateToRepository(ItemUpdateType.None, CancellationToken.None);
}
return Task.FromResult(true);
}
private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
{
var children = ActualChildren.ToList();
@ -691,9 +696,9 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <param name="path">The path.</param>
/// <returns><c>true</c> if the specified path is offline; otherwise, <c>false</c>.</returns>
private bool IsPathOffline(string path)
public static bool IsPathOffline(string path)
{
if (File.Exists(path))
if (FileSystem.FileExists(path))
{
return false;
}
@ -703,7 +708,7 @@ namespace MediaBrowser.Controller.Entities
// Depending on whether the path is local or unc, it may return either null or '\' at the top
while (!string.IsNullOrEmpty(path) && path.Length > 1)
{
if (Directory.Exists(path))
if (FileSystem.DirectoryExists(path))
{
return false;
}
@ -725,12 +730,12 @@ namespace MediaBrowser.Controller.Entities
/// <param name="folders">The folders.</param>
/// <param name="path">The path.</param>
/// <returns><c>true</c> if the specified folders contains path; otherwise, <c>false</c>.</returns>
private bool ContainsPath(IEnumerable<VirtualFolderInfo> folders, string path)
private static bool ContainsPath(IEnumerable<VirtualFolderInfo> folders, string path)
{
return folders.SelectMany(i => i.Locations).Any(i => ContainsPath(i, path));
}
private bool ContainsPath(string parent, string path)
private static bool ContainsPath(string parent, string path)
{
return string.Equals(parent, path, StringComparison.OrdinalIgnoreCase) || FileSystem.ContainsSubPath(parent, path);
}
@ -752,21 +757,24 @@ namespace MediaBrowser.Controller.Entities
/// <returns>IEnumerable{BaseItem}.</returns>
protected IEnumerable<BaseItem> GetCachedChildren()
{
var childrenItems = ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null);
if (ConfigurationManager.Configuration.DisableStartupScan)
{
return ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null);
//return ItemRepository.GetItems(new InternalItemsQuery
//{
// ParentId = Id
//var children = ItemRepository.GetChildren(Id).Select(RetrieveChild).Where(i => i != null).ToList();
//if (children.Count != childrenItems.Count)
//{
// var b = this;
//}
return childrenItems;
//}).Items.Select(RetrieveChild).Where(i => i != null);
}
else
{
return ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null);
}
}
private BaseItem RetrieveChild(BaseItem child)
{
if (child.Id == Guid.Empty)
if (child == null || child.Id == Guid.Empty)
{
Logger.Error("Item found with empty Id: " + (child.Path ?? child.Name));
return null;
@ -1064,7 +1072,7 @@ namespace MediaBrowser.Controller.Entities
}
}
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var changesFound = false;
@ -1085,7 +1093,7 @@ namespace MediaBrowser.Controller.Entities
/// Refreshes the linked children.
/// </summary>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
private bool RefreshLinkedChildren(IEnumerable<FileSystemInfo> fileSystemChildren)
private bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
{
var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
@ -1170,9 +1178,16 @@ namespace MediaBrowser.Controller.Entities
DateTime? datePlayed,
bool resetPosition)
{
var itemsResult = await GetItems(new InternalItemsQuery
{
User = user,
Recursive = true,
IsFolder = false
}).ConfigureAwait(false);
// Sweep through recursively and update status
var tasks = GetRecursiveChildren(user, i => !i.IsFolder && i.LocationType != LocationType.Virtual)
.Select(c => c.MarkPlayed(user, datePlayed, resetPosition));
var tasks = itemsResult.Items.Select(c => c.MarkPlayed(user, datePlayed, resetPosition));
await Task.WhenAll(tasks).ConfigureAwait(false);
}
@ -1184,9 +1199,16 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns>
public override async Task MarkUnplayed(User user)
{
var itemsResult = await GetItems(new InternalItemsQuery
{
User = user,
Recursive = true,
IsFolder = false
}).ConfigureAwait(false);
// Sweep through recursively and update status
var tasks = GetRecursiveChildren(user, i => !i.IsFolder && i.LocationType != LocationType.Virtual)
.Select(c => c.MarkUnplayed(user));
var tasks = itemsResult.Items.Select(c => c.MarkUnplayed(user));
await Task.WhenAll(tasks).ConfigureAwait(false);
}

View file

@ -8,19 +8,11 @@ using System.Linq;
namespace MediaBrowser.Controller.Entities
{
public class Game : BaseItem, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, ISupportsPlaceHolders, IHasPreferredMetadataLanguage, IHasLookupInfo<GameInfo>
public class Game : BaseItem, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo<GameInfo>
{
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
public string PreferredMetadataLanguage { get; set; }
/// <summary>
/// Gets or sets the preferred metadata country code.
/// </summary>
/// <value>The preferred metadata country code.</value>
public string PreferredMetadataCountryCode { get; set; }
public Game()
{
MultiPartGameFiles = new List<string>();

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