diff options
Diffstat (limited to 'MediaBrowser.Common.Implementations')
7 files changed, 383 insertions, 18 deletions
diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index daa664a38..ee22b7baa 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -1,5 +1,4 @@ -using System.Net; -using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Implementations.Archiving; using MediaBrowser.Common.Implementations.IO; @@ -7,6 +6,7 @@ using MediaBrowser.Common.Implementations.ScheduledTasks; using MediaBrowser.Common.Implementations.Security; using MediaBrowser.Common.Implementations.Serialization; using MediaBrowser.Common.Implementations.Updates; +using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.ScheduledTasks; @@ -21,6 +21,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Net.Http; using System.Reflection; using System.Threading; @@ -150,15 +151,17 @@ namespace MediaBrowser.Common.Implementations /// Gets or sets the installation manager. /// </summary> /// <value>The installation manager.</value> - protected IInstallationManager InstallationManager { get; set; } + protected IInstallationManager InstallationManager { get; private set; } + protected IFileSystem FileSystemManager { get; private set; } + /// <summary> /// Gets or sets the zip client. /// </summary> /// <value>The zip client.</value> - protected IZipClient ZipClient { get; set; } + protected IZipClient ZipClient { get; private set; } - protected IIsoManager IsoManager { get; set; } + protected IIsoManager IsoManager { get; private set; } /// <summary> /// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class. @@ -347,7 +350,10 @@ namespace MediaBrowser.Common.Implementations RegisterSingleInstance(TaskManager); - HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, CreateHttpClient); + FileSystemManager = CreateFileSystemManager(); + RegisterSingleInstance(FileSystemManager); + + HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, CreateHttpClient, FileSystemManager); RegisterSingleInstance(HttpClient); NetworkManager = CreateNetworkManager(); @@ -367,6 +373,11 @@ namespace MediaBrowser.Common.Implementations }); } + protected virtual IFileSystem CreateFileSystemManager() + { + return new CommonFileSystem(Logger, true); + } + protected abstract HttpClient CreateHttpClient(bool enableHttpCompression); /// <summary> diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index b75234107..0d6ba5c1d 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -34,6 +34,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager public delegate HttpClient GetHttpClientHandler(bool enableHttpCompression); private readonly GetHttpClientHandler _getHttpClientHandler; + private readonly IFileSystem _fileSystem; /// <summary> /// Initializes a new instance of the <see cref="HttpClientManager"/> class. @@ -46,7 +47,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// or /// logger /// </exception> - public HttpClientManager(IApplicationPaths appPaths, ILogger logger, GetHttpClientHandler getHttpClientHandler) + public HttpClientManager(IApplicationPaths appPaths, ILogger logger, GetHttpClientHandler getHttpClientHandler, IFileSystem fileSystem) { if (appPaths == null) { @@ -59,6 +60,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager _logger = logger; _getHttpClientHandler = getHttpClientHandler; + _fileSystem = fileSystem; _appPaths = appPaths; } @@ -417,7 +419,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager // We're not able to track progress using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { - using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); } @@ -427,7 +429,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager { using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), options.Progress.Report, contentLength.Value)) { - using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs new file mode 100644 index 000000000..ed9baf3b2 --- /dev/null +++ b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs @@ -0,0 +1,342 @@ +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; + + public CommonFileSystem(ILogger logger, bool supportsAsyncFileStreams) + { + Logger = logger; + _supportsAsyncFileStreams = supportsAsyncFileStreams; + } + + /// <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)) + { + return File.ReadAllText(filename); + } + + 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) + { + // 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> + /// The invalid file name chars + /// </summary> + private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars(); + + /// <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> + /// <param name="logger">The logger.</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, 4096, true); + } + + return new FileStream(path, mode, access, share); + } + } + + /// <summary> + /// Adapted from http://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java + /// </summary> + internal class WindowsShortcut + { + public bool IsDirectory { get; private set; } + public bool IsLocal { get; private set; } + public string ResolvedPath { get; private set; } + + public WindowsShortcut(string file) + { + ParseLink(File.ReadAllBytes(file), Encoding.UTF8); + } + + private static bool isMagicPresent(byte[] link) + { + + const int magic = 0x0000004C; + const int magic_offset = 0x00; + + return link.Length >= 32 && bytesToDword(link, magic_offset) == magic; + } + + /** + * Gobbles up link data by parsing it and storing info in member fields + * @param link all the bytes from the .lnk file + */ + private void ParseLink(byte[] link, Encoding encoding) + { + if (!isMagicPresent(link)) + throw new IOException("Invalid shortcut; magic is missing", 0); + + // get the flags byte + byte flags = link[0x14]; + + // get the file attributes byte + const int file_atts_offset = 0x18; + byte file_atts = link[file_atts_offset]; + byte is_dir_mask = (byte)0x10; + if ((file_atts & is_dir_mask) > 0) + { + IsDirectory = true; + } + else + { + IsDirectory = false; + } + + // if the shell settings are present, skip them + const int shell_offset = 0x4c; + const byte has_shell_mask = (byte)0x01; + int shell_len = 0; + if ((flags & has_shell_mask) > 0) + { + // the plus 2 accounts for the length marker itself + shell_len = bytesToWord(link, shell_offset) + 2; + } + + // get to the file settings + int file_start = 0x4c + shell_len; + + const int file_location_info_flag_offset_offset = 0x08; + int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset]; + IsLocal = (file_location_info_flag & 2) == 0; + // get the local volume and local system values + //final int localVolumeTable_offset_offset = 0x0C; + const int basename_offset_offset = 0x10; + const int networkVolumeTable_offset_offset = 0x14; + const int finalname_offset_offset = 0x18; + int finalname_offset = link[file_start + finalname_offset_offset] + file_start; + String finalname = getNullDelimitedString(link, finalname_offset, encoding); + if (IsLocal) + { + int basename_offset = link[file_start + basename_offset_offset] + file_start; + String basename = getNullDelimitedString(link, basename_offset, encoding); + ResolvedPath = basename + finalname; + } + else + { + int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start; + int shareName_offset_offset = 0x08; + int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset] + + networkVolumeTable_offset; + String shareName = getNullDelimitedString(link, shareName_offset, encoding); + ResolvedPath = shareName + "\\" + finalname; + } + } + + private static string getNullDelimitedString(byte[] bytes, int off, Encoding encoding) + { + int len = 0; + + // count bytes until the null character (0) + while (true) + { + if (bytes[off + len] == 0) + { + break; + } + len++; + } + + return encoding.GetString(bytes, off, len); + } + + /* + * convert two bytes into a short note, this is little endian because it's + * for an Intel only OS. + */ + private static int bytesToWord(byte[] bytes, int off) + { + return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); + } + + private static int bytesToDword(byte[] bytes, int off) + { + return (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off); + } + + } + +} diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index 3ec330d9c..9e48f3b3e 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -44,6 +44,10 @@ <Reference Include="SharpCompress"> <HintPath>..\packages\sharpcompress.0.10.1.3\lib\net40\SharpCompress.dll</HintPath> </Reference> + <Reference Include="SimpleInjector, Version=2.3.6.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\SimpleInjector.2.3.6\lib\net40-client\SimpleInjector.dll</HintPath> + </Reference> <Reference Include="System" /> <Reference Include="System.Configuration" /> <Reference Include="System.Core" /> @@ -54,9 +58,6 @@ <Reference Include="ServiceStack.Text"> <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath> </Reference> - <Reference Include="SimpleInjector"> - <HintPath>..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll</HintPath> - </Reference> </ItemGroup> <ItemGroup> <Compile Include="..\SharedVersion.cs"> @@ -68,6 +69,7 @@ <Compile Include="Configuration\BaseConfigurationManager.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" /> diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 812269ea8..e04cadfc5 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -1,12 +1,13 @@ using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Logging; namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks { @@ -23,14 +24,17 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + /// <summary> /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class. /// </summary> /// <param name="appPaths">The app paths.</param> - public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger) + public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) { ApplicationPaths = appPaths; _logger = logger; + _fileSystem = fileSystem; } /// <summary> @@ -94,7 +98,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress) { var filesToDelete = new DirectoryInfo(directory).EnumerateFiles("*", SearchOption.AllDirectories) - .Where(f => f.LastWriteTimeUtc < minDateModified) + .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) .ToList(); var index = 0; diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index bfd626adb..7c7833ae6 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; using MediaBrowser.Common.ScheduledTasks; using System; using System.Collections.Generic; @@ -20,13 +21,16 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// <value>The configuration manager.</value> private IConfigurationManager ConfigurationManager { get; set; } + private readonly IFileSystem _fileSystem; + /// <summary> /// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class. /// </summary> /// <param name="configurationManager">The configuration manager.</param> - public DeleteLogFileTask(IConfigurationManager configurationManager) + public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem) { ConfigurationManager = configurationManager; + _fileSystem = fileSystem; } /// <summary> @@ -58,7 +62,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays)); var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories) - .Where(f => f.LastWriteTimeUtc < minDateModified) + .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) .ToList(); var index = 0; diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config index 8716325bf..f2fe48830 100644 --- a/MediaBrowser.Common.Implementations/packages.config +++ b/MediaBrowser.Common.Implementations/packages.config @@ -3,5 +3,5 @@ <package id="NLog" version="2.1.0" targetFramework="net45" /> <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" /> <package id="sharpcompress" version="0.10.1.3" targetFramework="net45" /> - <package id="SimpleInjector" version="2.3.5" targetFramework="net45" /> + <package id="SimpleInjector" version="2.3.6" targetFramework="net45" /> </packages>
\ No newline at end of file |
