Created ILibraryMonitor to replace IDirectoryWatchers

pull/702/head
Luke Pulverenti 11 years ago
parent 2ae17a8d52
commit 7c5b222463

@ -187,7 +187,7 @@ namespace MediaBrowser.Api.Library
/// </summary> /// </summary>
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IDirectoryWatchers _directoryWatchers; private readonly ILibraryMonitor _libraryMonitor;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -199,7 +199,7 @@ namespace MediaBrowser.Api.Library
/// <param name="userManager">The user manager.</param> /// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
/// <exception cref="System.ArgumentNullException">appPaths</exception> /// <exception cref="System.ArgumentNullException">appPaths</exception>
public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger)
{ {
if (appPaths == null) if (appPaths == null)
{ {
@ -209,7 +209,7 @@ namespace MediaBrowser.Api.Library
_userManager = userManager; _userManager = userManager;
_appPaths = appPaths; _appPaths = appPaths;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_directoryWatchers = directoryWatchers; _libraryMonitor = libraryMonitor;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_logger = logger; _logger = logger;
} }
@ -270,8 +270,7 @@ namespace MediaBrowser.Api.Library
throw new ArgumentException("There is already a media collection with the name " + name + "."); throw new ArgumentException("There is already a media collection with the name " + name + ".");
} }
_directoryWatchers.Stop(); _libraryMonitor.Stop();
_directoryWatchers.TemporarilyIgnore(virtualFolderPath);
try try
{ {
@ -294,10 +293,8 @@ namespace MediaBrowser.Api.Library
// No need to start if scanning the library because it will handle it // No need to start if scanning the library because it will handle it
if (!request.RefreshLibrary) if (!request.RefreshLibrary)
{ {
_directoryWatchers.Start(); _libraryMonitor.Start();
} }
_directoryWatchers.RemoveTempIgnore(virtualFolderPath);
} }
if (request.RefreshLibrary) if (request.RefreshLibrary)
@ -348,9 +345,7 @@ namespace MediaBrowser.Api.Library
throw new ArgumentException("There is already a media collection with the name " + newPath + "."); throw new ArgumentException("There is already a media collection with the name " + newPath + ".");
} }
_directoryWatchers.Stop(); _libraryMonitor.Stop();
_directoryWatchers.TemporarilyIgnore(currentPath);
_directoryWatchers.TemporarilyIgnore(newPath);
try try
{ {
@ -376,11 +371,8 @@ namespace MediaBrowser.Api.Library
// No need to start if scanning the library because it will handle it // No need to start if scanning the library because it will handle it
if (!request.RefreshLibrary) if (!request.RefreshLibrary)
{ {
_directoryWatchers.Start(); _libraryMonitor.Start();
} }
_directoryWatchers.RemoveTempIgnore(currentPath);
_directoryWatchers.RemoveTempIgnore(newPath);
} }
if (request.RefreshLibrary) if (request.RefreshLibrary)
@ -420,8 +412,7 @@ namespace MediaBrowser.Api.Library
throw new DirectoryNotFoundException("The media folder does not exist"); throw new DirectoryNotFoundException("The media folder does not exist");
} }
_directoryWatchers.Stop(); _libraryMonitor.Stop();
_directoryWatchers.TemporarilyIgnore(path);
try try
{ {
@ -437,10 +428,8 @@ namespace MediaBrowser.Api.Library
// No need to start if scanning the library because it will handle it // No need to start if scanning the library because it will handle it
if (!request.RefreshLibrary) if (!request.RefreshLibrary)
{ {
_directoryWatchers.Start(); _libraryMonitor.Start();
} }
_directoryWatchers.RemoveTempIgnore(path);
} }
if (request.RefreshLibrary) if (request.RefreshLibrary)
@ -460,7 +449,7 @@ namespace MediaBrowser.Api.Library
throw new ArgumentNullException("request"); throw new ArgumentNullException("request");
} }
_directoryWatchers.Stop(); _libraryMonitor.Stop();
try try
{ {
@ -485,7 +474,7 @@ namespace MediaBrowser.Api.Library
// No need to start if scanning the library because it will handle it // No need to start if scanning the library because it will handle it
if (!request.RefreshLibrary) if (!request.RefreshLibrary)
{ {
_directoryWatchers.Start(); _libraryMonitor.Start();
} }
} }
@ -506,7 +495,7 @@ namespace MediaBrowser.Api.Library
throw new ArgumentNullException("request"); throw new ArgumentNullException("request");
} }
_directoryWatchers.Stop(); _libraryMonitor.Stop();
try try
{ {
@ -531,7 +520,7 @@ namespace MediaBrowser.Api.Library
// No need to start if scanning the library because it will handle it // No need to start if scanning the library because it will handle it
if (!request.RefreshLibrary) if (!request.RefreshLibrary)
{ {
_directoryWatchers.Start(); _libraryMonitor.Start();
} }
} }

@ -1,29 +0,0 @@
using System;
namespace MediaBrowser.Controller.IO
{
public interface IDirectoryWatchers : IDisposable
{
/// <summary>
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
/// </summary>
/// <param name="path">The path.</param>
void TemporarilyIgnore(string path);
/// <summary>
/// Removes the temp ignore.
/// </summary>
/// <param name="path">The path.</param>
void RemoveTempIgnore(string path);
/// <summary>
/// Starts this instance.
/// </summary>
void Start();
/// <summary>
/// Stops this instance.
/// </summary>
void Stop();
}
}

@ -0,0 +1,36 @@
using System;
namespace MediaBrowser.Controller.Library
{
public interface ILibraryMonitor : IDisposable
{
/// <summary>
/// Starts this instance.
/// </summary>
void Start();
/// <summary>
/// Stops this instance.
/// </summary>
void Stop();
/// <summary>
/// Reports the file system change beginning.
/// </summary>
/// <param name="path">The path.</param>
void ReportFileSystemChangeBeginning(string path);
/// <summary>
/// Reports the file system change complete.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="refreshPath">if set to <c>true</c> [refresh path].</param>
void ReportFileSystemChangeComplete(string path, bool refreshPath);
/// <summary>
/// Reports the file system changed.
/// </summary>
/// <param name="path">The path.</param>
void ReportFileSystemChanged(string path);
}
}

@ -182,7 +182,7 @@
<Compile Include="Entities\Video.cs" /> <Compile Include="Entities\Video.cs" />
<Compile Include="Entities\CollectionFolder.cs" /> <Compile Include="Entities\CollectionFolder.cs" />
<Compile Include="Entities\Year.cs" /> <Compile Include="Entities\Year.cs" />
<Compile Include="IO\IDirectoryWatchers.cs" /> <Compile Include="Library\ILibraryMonitor.cs" />
<Compile Include="IServerApplicationHost.cs" /> <Compile Include="IServerApplicationHost.cs" />
<Compile Include="IServerApplicationPaths.cs" /> <Compile Include="IServerApplicationPaths.cs" />
<Compile Include="Library\SearchHintInfo.cs" /> <Compile Include="Library\SearchHintInfo.cs" />

@ -165,7 +165,7 @@ namespace MediaBrowser.Model.Configuration
/// different directories and files. /// different directories and files.
/// </summary> /// </summary>
/// <value>The file watcher delay.</value> /// <value>The file watcher delay.</value>
public int FileWatcherDelay { get; set; } public int RealtimeWatcherDelay { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether [enable dashboard response caching]. /// Gets or sets a value indicating whether [enable dashboard response caching].
@ -250,7 +250,7 @@ namespace MediaBrowser.Model.Configuration
MaxResumePct = 90; MaxResumePct = 90;
MinResumeDurationSeconds = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalSeconds); MinResumeDurationSeconds = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalSeconds);
FileWatcherDelay = 8; RealtimeWatcherDelay = 20;
RecentItemDays = 10; RecentItemDays = 10;

@ -3,7 +3,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager
/// <summary> /// <summary>
/// The _directory watchers /// The _directory watchers
/// </summary> /// </summary>
private readonly IDirectoryWatchers _directoryWatchers; private readonly ILibraryMonitor _libraryMonitor;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -44,11 +44,11 @@ namespace MediaBrowser.Providers.Manager
/// Initializes a new instance of the <see cref="ImageSaver"/> class. /// Initializes a new instance of the <see cref="ImageSaver"/> class.
/// </summary> /// </summary>
/// <param name="config">The config.</param> /// <param name="config">The config.</param>
/// <param name="directoryWatchers">The directory watchers.</param> /// <param name="libraryMonitor">The directory watchers.</param>
public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger)
{ {
_config = config; _config = config;
_directoryWatchers = directoryWatchers; _libraryMonitor = libraryMonitor;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_logger = logger; _logger = logger;
_remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath); _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath);
@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.Manager
// Delete the current path // Delete the current path
if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase)) if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase))
{ {
_directoryWatchers.TemporarilyIgnore(currentPath); _libraryMonitor.ReportFileSystemChangeBeginning(currentPath);
try try
{ {
@ -179,7 +179,7 @@ namespace MediaBrowser.Providers.Manager
} }
finally finally
{ {
_directoryWatchers.RemoveTempIgnore(currentPath); _libraryMonitor.ReportFileSystemChangeComplete(currentPath, false);
} }
} }
} }
@ -197,8 +197,8 @@ namespace MediaBrowser.Providers.Manager
var parentFolder = Path.GetDirectoryName(path); var parentFolder = Path.GetDirectoryName(path);
_directoryWatchers.TemporarilyIgnore(path); _libraryMonitor.ReportFileSystemChangeBeginning(path);
_directoryWatchers.TemporarilyIgnore(parentFolder); _libraryMonitor.ReportFileSystemChangeBeginning(parentFolder);
try try
{ {
@ -223,8 +223,8 @@ namespace MediaBrowser.Providers.Manager
} }
finally finally
{ {
_directoryWatchers.RemoveTempIgnore(path); _libraryMonitor.ReportFileSystemChangeComplete(path, false);
_directoryWatchers.RemoveTempIgnore(parentFolder); _libraryMonitor.ReportFileSystemChangeComplete(parentFolder, false);
} }
} }

@ -38,7 +38,6 @@ namespace MediaBrowser.Providers.Manager
public void AddParts(IEnumerable<IMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders) public void AddParts(IEnumerable<IMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders)
{ {
_providers = providers.OfType<IMetadataProvider<TItemType>>() _providers = providers.OfType<IMetadataProvider<TItemType>>()
.OrderBy(GetSortOrder)
.ToArray(); .ToArray();
_imageProviders = imageProviders.OrderBy(i => i.Order).ToArray(); _imageProviders = imageProviders.OrderBy(i => i.Order).ToArray();
@ -179,21 +178,6 @@ namespace MediaBrowser.Providers.Manager
return providers; return providers;
} }
/// <summary>
/// Gets the sort order.
/// </summary>
/// <param name="provider">The provider.</param>
/// <returns>System.Int32.</returns>
protected virtual int GetSortOrder(IMetadataProvider<TItemType> provider)
{
if (provider is IRemoteMetadataProvider)
{
return 1;
}
return 0;
}
/// <summary> /// <summary>
/// Determines whether this instance can refresh the specified provider. /// Determines whether this instance can refresh the specified provider.
/// </summary> /// </summary>
@ -217,7 +201,7 @@ namespace MediaBrowser.Providers.Manager
protected abstract Task SaveItem(TItemType item, ItemUpdateType reason, CancellationToken cancellationToken); protected abstract Task SaveItem(TItemType item, ItemUpdateType reason, CancellationToken cancellationToken);
protected virtual ItemId GetId(TItemType item) protected virtual ItemId GetId(IHasMetadata item)
{ {
return new ItemId return new ItemId
{ {

@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager
/// <summary> /// <summary>
/// The _directory watchers /// The _directory watchers
/// </summary> /// </summary>
private readonly IDirectoryWatchers _directoryWatchers; private readonly ILibraryMonitor _libraryMonitor;
/// <summary> /// <summary>
/// Gets or sets the configuration manager. /// Gets or sets the configuration manager.
@ -57,23 +57,23 @@ namespace MediaBrowser.Providers.Manager
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
private IMetadataService[] _metadataServices = {}; private IMetadataService[] _metadataServices = { };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProviderManager" /> class. /// Initializes a new instance of the <see cref="ProviderManager" /> class.
/// </summary> /// </summary>
/// <param name="httpClient">The HTTP client.</param> /// <param name="httpClient">The HTTP client.</param>
/// <param name="configurationManager">The configuration manager.</param> /// <param name="configurationManager">The configuration manager.</param>
/// <param name="directoryWatchers">The directory watchers.</param> /// <param name="libraryMonitor">The directory watchers.</param>
/// <param name="logManager">The log manager.</param> /// <param name="logManager">The log manager.</param>
/// <param name="fileSystem">The file system.</param> /// <param name="fileSystem">The file system.</param>
/// <param name="itemRepo">The item repo.</param> /// <param name="itemRepo">The item repo.</param>
public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo)
{ {
_logger = logManager.GetLogger("ProviderManager"); _logger = logManager.GetLogger("ProviderManager");
_httpClient = httpClient; _httpClient = httpClient;
ConfigurationManager = configurationManager; ConfigurationManager = configurationManager;
_directoryWatchers = directoryWatchers; _libraryMonitor = libraryMonitor;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_itemRepo = itemRepo; _itemRepo = itemRepo;
} }
@ -315,7 +315,7 @@ namespace MediaBrowser.Providers.Manager
} }
//Tell the watchers to ignore //Tell the watchers to ignore
_directoryWatchers.TemporarilyIgnore(path); _libraryMonitor.ReportFileSystemChangeBeginning(path);
if (dataToSave.CanSeek) if (dataToSave.CanSeek)
{ {
@ -338,7 +338,7 @@ namespace MediaBrowser.Providers.Manager
finally finally
{ {
//Remove the ignore //Remove the ignore
_directoryWatchers.RemoveTempIgnore(path); _libraryMonitor.ReportFileSystemChangeComplete(path, false);
} }
} }
@ -380,7 +380,7 @@ namespace MediaBrowser.Providers.Manager
/// <returns>Task.</returns> /// <returns>Task.</returns>
public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken) public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken)
{ {
return new ImageSaver(ConfigurationManager, _directoryWatchers, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken);
} }
/// <summary> /// <summary>

@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{ {
public class EpisodeFileOrganizer public class EpisodeFileOrganizer
{ {
private readonly IDirectoryWatchers _directoryWatchers; private readonly ILibraryMonitor _libraryMonitor;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
@ -31,14 +31,14 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers) public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor)
{ {
_organizationService = organizationService; _organizationService = organizationService;
_config = config; _config = config;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_logger = logger; _logger = logger;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_directoryWatchers = directoryWatchers; _libraryMonitor = libraryMonitor;
} }
public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, TvFileOrganizationOptions options, bool overwriteExisting) public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, TvFileOrganizationOptions options, bool overwriteExisting)
@ -174,6 +174,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{ {
_logger.Debug("Removing duplicate episode {0}", path); _logger.Debug("Removing duplicate episode {0}", path);
_libraryMonitor.ReportFileSystemChangeBeginning(path);
try try
{ {
File.Delete(path); File.Delete(path);
@ -182,6 +184,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{ {
_logger.ErrorException("Error removing duplicate episode", ex, path); _logger.ErrorException("Error removing duplicate episode", ex, path);
} }
finally
{
_libraryMonitor.ReportFileSystemChangeComplete(path, true);
}
} }
} }
} }
@ -232,7 +238,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result) private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result)
{ {
_directoryWatchers.TemporarilyIgnore(result.TargetPath); _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath);
Directory.CreateDirectory(Path.GetDirectoryName(result.TargetPath)); Directory.CreateDirectory(Path.GetDirectoryName(result.TargetPath));
@ -264,7 +270,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
} }
finally finally
{ {
_directoryWatchers.RemoveTempIgnore(result.TargetPath); _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true);
} }
if (copy) if (copy)

@ -21,17 +21,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
private readonly IFileOrganizationRepository _repo; private readonly IFileOrganizationRepository _repo;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IDirectoryWatchers _directoryWatchers; private readonly ILibraryMonitor _libraryMonitor;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem)
{ {
_taskManager = taskManager; _taskManager = taskManager;
_repo = repo; _repo = repo;
_logger = logger; _logger = logger;
_directoryWatchers = directoryWatchers; _libraryMonitor = libraryMonitor;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_config = config; _config = config;
_fileSystem = fileSystem; _fileSystem = fileSystem;
@ -91,13 +91,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
} }
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
_directoryWatchers); _libraryMonitor);
await organizer.OrganizeEpisodeFile(result.OriginalPath, _config.Configuration.TvFileOrganizationOptions, true) await organizer.OrganizeEpisodeFile(result.OriginalPath, _config.Configuration.TvFileOrganizationOptions, true)
.ConfigureAwait(false); .ConfigureAwait(false);
await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None)
.ConfigureAwait(false);
} }
public Task ClearLog() public Task ClearLog()
@ -108,12 +105,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request) public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request)
{ {
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
_directoryWatchers); _libraryMonitor);
await organizer.OrganizeWithCorrection(request, _config.Configuration.TvFileOrganizationOptions).ConfigureAwait(false); await organizer.OrganizeWithCorrection(request, _config.Configuration.TvFileOrganizationOptions).ConfigureAwait(false);
await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None)
.ConfigureAwait(false);
} }
} }
} }

@ -14,16 +14,16 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{ {
public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask
{ {
private readonly IDirectoryWatchers _directoryWatchers; private readonly ILibraryMonitor _libraryMonitor;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IFileOrganizationService _organizationService; private readonly IFileOrganizationService _organizationService;
public OrganizerScheduledTask(IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService) public OrganizerScheduledTask(ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService)
{ {
_directoryWatchers = directoryWatchers; _libraryMonitor = libraryMonitor;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = logger; _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{ {
return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _directoryWatchers, _organizationService, _config) return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config)
.Organize(_config.Configuration.TvFileOrganizationOptions, cancellationToken, progress); .Organize(_config.Configuration.TvFileOrganizationOptions, cancellationToken, progress);
} }

@ -18,19 +18,19 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{ {
public class TvFolderOrganizer public class TvFolderOrganizer
{ {
private readonly IDirectoryWatchers _directoryWatchers; private readonly ILibraryMonitor _libraryMonitor;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IFileOrganizationService _organizationService; private readonly IFileOrganizationService _organizationService;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IDirectoryWatchers directoryWatchers, IFileOrganizationService organizationService, IServerConfigurationManager config) public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = logger; _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_directoryWatchers = directoryWatchers; _libraryMonitor = libraryMonitor;
_organizationService = organizationService; _organizationService = organizationService;
_config = config; _config = config;
} }
@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
foreach (var file in eligibleFiles) foreach (var file in eligibleFiles)
{ {
var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager,
_directoryWatchers); _libraryMonitor);
var result = await organizer.OrganizeEpisodeFile(file.FullName, options, false).ConfigureAwait(false); var result = await organizer.OrganizeEpisodeFile(file.FullName, options, false).ConfigureAwait(false);

@ -1,8 +1,6 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -18,10 +16,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.IO namespace MediaBrowser.Server.Implementations.IO
{ {
/// <summary> public class LibraryMonitor : ILibraryMonitor
/// Class DirectoryWatchers
/// </summary>
public class DirectoryWatchers : IDirectoryWatchers
{ {
/// <summary> /// <summary>
/// The file system watchers /// The file system watchers
@ -55,7 +50,7 @@ namespace MediaBrowser.Server.Implementations.IO
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
public void TemporarilyIgnore(string path) private void TemporarilyIgnore(string path)
{ {
_tempIgnoredPaths[path] = path; _tempIgnoredPaths[path] = path;
} }
@ -64,7 +59,7 @@ namespace MediaBrowser.Server.Implementations.IO
/// Removes the temp ignore. /// Removes the temp ignore.
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
public async void RemoveTempIgnore(string path) private async void RemoveTempIgnore(string path)
{ {
// This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called.
// Seeing long delays in some situations, especially over the network. // Seeing long delays in some situations, especially over the network.
@ -75,6 +70,21 @@ namespace MediaBrowser.Server.Implementations.IO
_tempIgnoredPaths.TryRemove(path, out val); _tempIgnoredPaths.TryRemove(path, out val);
} }
public void ReportFileSystemChangeBeginning(string path)
{
TemporarilyIgnore(path);
}
public void ReportFileSystemChangeComplete(string path, bool refreshPath)
{
RemoveTempIgnore(path);
if (refreshPath)
{
ReportFileSystemChanged(path);
}
}
/// <summary> /// <summary>
/// Gets or sets the logger. /// Gets or sets the logger.
/// </summary> /// </summary>
@ -90,12 +100,10 @@ namespace MediaBrowser.Server.Implementations.IO
private ILibraryManager LibraryManager { get; set; } private ILibraryManager LibraryManager { get; set; }
private IServerConfigurationManager ConfigurationManager { get; set; } private IServerConfigurationManager ConfigurationManager { get; set; }
private readonly IFileSystem _fileSystem;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DirectoryWatchers" /> class. /// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
/// </summary> /// </summary>
public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager)
{ {
if (taskManager == null) if (taskManager == null)
{ {
@ -104,9 +112,8 @@ namespace MediaBrowser.Server.Implementations.IO
LibraryManager = libraryManager; LibraryManager = libraryManager;
TaskManager = taskManager; TaskManager = taskManager;
Logger = logManager.GetLogger("DirectoryWatchers"); Logger = logManager.GetLogger(GetType().Name);
ConfigurationManager = configurationManager; ConfigurationManager = configurationManager;
_fileSystem = fileSystem;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
} }
@ -328,31 +335,25 @@ namespace MediaBrowser.Server.Implementations.IO
{ {
OnWatcherChanged(e); OnWatcherChanged(e);
} }
catch (IOException ex) catch (Exception ex)
{ {
Logger.ErrorException("IOException in watcher changed. Path: {0}", ex, e.FullPath); Logger.ErrorException("Exception in watcher changed. Path: {0}", ex, e.FullPath);
} }
} }
private void OnWatcherChanged(FileSystemEventArgs e) private void OnWatcherChanged(FileSystemEventArgs e)
{ {
var name = e.Name; Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath);
// Ignore certain files ReportFileSystemChanged(e.FullPath);
if (_alwaysIgnoreFiles.Contains(name, StringComparer.OrdinalIgnoreCase)) }
{
return;
}
var nameFromFullPath = Path.GetFileName(e.FullPath); public void ReportFileSystemChanged(string path)
// Ignore certain files {
if (!string.IsNullOrEmpty(nameFromFullPath) && _alwaysIgnoreFiles.Contains(nameFromFullPath, StringComparer.OrdinalIgnoreCase)) var filename = Path.GetFileName(path);
{
return;
}
// Ignore when someone manually creates a new folder // Ignore certain files
if (e.ChangeType == WatcherChangeTypes.Created && name == "New folder") if (!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase))
{ {
return; return;
} }
@ -362,17 +363,17 @@ namespace MediaBrowser.Server.Implementations.IO
// If the parent of an ignored path has a change event, ignore that too // If the parent of an ignored path has a change event, ignore that too
if (tempIgnorePaths.Any(i => if (tempIgnorePaths.Any(i =>
{ {
if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase)) if (string.Equals(i, path, StringComparison.OrdinalIgnoreCase))
{ {
Logger.Debug("Watcher ignoring change to {0}", e.FullPath); Logger.Debug("Ignoring change to {0}", path);
return true; return true;
} }
// Go up a level // Go up a level
var parent = Path.GetDirectoryName(i); var parent = Path.GetDirectoryName(i);
if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase))
{ {
Logger.Debug("Watcher ignoring change to {0}", e.FullPath); Logger.Debug("Ignoring change to {0}", path);
return true; return true;
} }
@ -380,17 +381,17 @@ namespace MediaBrowser.Server.Implementations.IO
if (!string.IsNullOrEmpty(parent)) if (!string.IsNullOrEmpty(parent))
{ {
parent = Path.GetDirectoryName(i); parent = Path.GetDirectoryName(i);
if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase))
{ {
Logger.Debug("Watcher ignoring change to {0}", e.FullPath); Logger.Debug("Ignoring change to {0}", path);
return true; return true;
} }
} }
if (i.StartsWith(e.FullPath, StringComparison.OrdinalIgnoreCase) || if (i.StartsWith(path, StringComparison.OrdinalIgnoreCase) ||
e.FullPath.StartsWith(i, StringComparison.OrdinalIgnoreCase)) path.StartsWith(i, StringComparison.OrdinalIgnoreCase))
{ {
Logger.Debug("Watcher ignoring change to {0}", e.FullPath); Logger.Debug("Ignoring change to {0}", path);
return true; return true;
} }
@ -401,22 +402,19 @@ namespace MediaBrowser.Server.Implementations.IO
return; return;
} }
Logger.Info("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); // Avoid implicitly captured closure
var affectedPath = path;
//Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath);
var affectedPath = e.FullPath;
_affectedPaths.AddOrUpdate(affectedPath, affectedPath, (key, oldValue) => affectedPath);
lock (_timerLock) lock (_timerLock)
{ {
if (_updateTimer == null) if (_updateTimer == null)
{ {
_updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1));
} }
else else
{ {
_updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1));
} }
} }
} }
@ -427,24 +425,9 @@ namespace MediaBrowser.Server.Implementations.IO
/// <param name="stateInfo">The state info.</param> /// <param name="stateInfo">The state info.</param>
private async void TimerStopped(object stateInfo) private async void TimerStopped(object stateInfo)
{ {
lock (_timerLock) Logger.Debug("Timer stopped.");
{
// Extend the timer as long as any of the paths are still being written to.
if (_affectedPaths.Any(p => IsFileLocked(p.Key)))
{
Logger.Info("Timer extended.");
_updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1));
return;
}
Logger.Info("Timer stopped."); DisposeTimer();
if (_updateTimer != null)
{
_updateTimer.Dispose();
_updateTimer = null;
}
}
var paths = _affectedPaths.Keys.ToList(); var paths = _affectedPaths.Keys.ToList();
_affectedPaths.Clear(); _affectedPaths.Clear();
@ -452,59 +435,16 @@ namespace MediaBrowser.Server.Implementations.IO
await ProcessPathChanges(paths).ConfigureAwait(false); await ProcessPathChanges(paths).ConfigureAwait(false);
} }
/// <summary> private void DisposeTimer()
/// Try and determine if a file is locked
/// This is not perfect, and is subject to race conditions, so I'd rather not make this a re-usable library method.
/// </summary>
/// <param name="path">The path.</param>
/// <returns><c>true</c> if [is file locked] [the specified path]; otherwise, <c>false</c>.</returns>
private bool IsFileLocked(string path)
{ {
try lock (_timerLock)
{
var data = _fileSystem.GetFileSystemInfo(path);
if (!data.Exists
|| data.Attributes.HasFlag(FileAttributes.Directory)
|| data.Attributes.HasFlag(FileAttributes.ReadOnly))
{
return false;
}
}
catch (IOException)
{
return false;
}
try
{ {
using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) if (_updateTimer != null)
{ {
//file is not locked _updateTimer.Dispose();
return false; _updateTimer = null;
} }
} }
catch (DirectoryNotFoundException)
{
return false;
}
catch (FileNotFoundException)
{
return false;
}
catch (IOException)
{
//the file is unavailable because it is:
//still being written to
//or being processed by another thread
//or does not exist (has already been processed)
Logger.Debug("{0} is locked.", path);
return true;
}
catch
{
return false;
}
} }
/// <summary> /// <summary>
@ -599,14 +539,7 @@ namespace MediaBrowser.Server.Implementations.IO
watcher.Dispose(); watcher.Dispose();
} }
lock (_timerLock) DisposeTimer();
{
if (_updateTimer != null)
{
_updateTimer.Dispose();
_updateTimer = null;
}
}
_fileSystemWatchers.Clear(); _fileSystemWatchers.Clear();
_affectedPaths.Clear(); _affectedPaths.Clear();

@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library
private IEnumerable<IMetadataSaver> _savers; private IEnumerable<IMetadataSaver> _savers;
private readonly Func<IDirectoryWatchers> _directoryWatchersFactory; private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
/// <summary> /// <summary>
/// The _library items cache /// The _library items cache
@ -180,14 +180,14 @@ namespace MediaBrowser.Server.Implementations.Library
/// <param name="userManager">The user manager.</param> /// <param name="userManager">The user manager.</param>
/// <param name="configurationManager">The configuration manager.</param> /// <param name="configurationManager">The configuration manager.</param>
/// <param name="userDataRepository">The user data repository.</param> /// <param name="userDataRepository">The user data repository.</param>
public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<IDirectoryWatchers> directoryWatchersFactory, IFileSystem fileSystem) public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem)
{ {
_logger = logger; _logger = logger;
_taskManager = taskManager; _taskManager = taskManager;
_userManager = userManager; _userManager = userManager;
ConfigurationManager = configurationManager; ConfigurationManager = configurationManager;
_userDataRepository = userDataRepository; _userDataRepository = userDataRepository;
_directoryWatchersFactory = directoryWatchersFactory; _libraryMonitorFactory = libraryMonitorFactory;
_fileSystem = fileSystem; _fileSystem = fileSystem;
ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>(); ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
@ -934,7 +934,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns>Task.</returns> /// <returns>Task.</returns>
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken) public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
{ {
_directoryWatchersFactory().Stop(); _libraryMonitorFactory().Stop();
try try
{ {
@ -942,7 +942,7 @@ namespace MediaBrowser.Server.Implementations.Library
} }
finally finally
{ {
_directoryWatchersFactory().Start(); _libraryMonitorFactory().Start();
} }
} }
@ -1462,13 +1462,13 @@ namespace MediaBrowser.Server.Implementations.Library
var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1)); var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1));
var directoryWatchers = _directoryWatchersFactory(); var directoryWatchers = _libraryMonitorFactory();
await semaphore.WaitAsync().ConfigureAwait(false); await semaphore.WaitAsync().ConfigureAwait(false);
try try
{ {
directoryWatchers.TemporarilyIgnore(path); directoryWatchers.ReportFileSystemChangeBeginning(path);
saver.Save(item, CancellationToken.None); saver.Save(item, CancellationToken.None);
} }
catch (Exception ex) catch (Exception ex)
@ -1477,7 +1477,7 @@ namespace MediaBrowser.Server.Implementations.Library
} }
finally finally
{ {
directoryWatchers.RemoveTempIgnore(path); directoryWatchers.ReportFileSystemChangeComplete(path, false);
semaphore.Release(); semaphore.Release();
} }
} }

@ -137,7 +137,7 @@
<Compile Include="HttpServer\StreamWriter.cs" /> <Compile Include="HttpServer\StreamWriter.cs" />
<Compile Include="HttpServer\SwaggerService.cs" /> <Compile Include="HttpServer\SwaggerService.cs" />
<Compile Include="Drawing\ImageProcessor.cs" /> <Compile Include="Drawing\ImageProcessor.cs" />
<Compile Include="IO\DirectoryWatchers.cs" /> <Compile Include="IO\LibraryMonitor.cs" />
<Compile Include="Library\CoreResolutionIgnoreRule.cs" /> <Compile Include="Library\CoreResolutionIgnoreRule.cs" />
<Compile Include="Library\LibraryManager.cs" /> <Compile Include="Library\LibraryManager.cs" />
<Compile Include="Library\SearchEngine.cs" /> <Compile Include="Library\SearchEngine.cs" />

@ -137,7 +137,7 @@ namespace MediaBrowser.ServerApplication
/// Gets or sets the directory watchers. /// Gets or sets the directory watchers.
/// </summary> /// </summary>
/// <value>The directory watchers.</value> /// <value>The directory watchers.</value>
private IDirectoryWatchers DirectoryWatchers { get; set; } private ILibraryMonitor LibraryMonitor { get; set; }
/// <summary> /// <summary>
/// Gets or sets the provider manager. /// Gets or sets the provider manager.
/// </summary> /// </summary>
@ -273,13 +273,13 @@ namespace MediaBrowser.ServerApplication
UserManager = new UserManager(Logger, ServerConfigurationManager, UserRepository); UserManager = new UserManager(Logger, ServerConfigurationManager, UserRepository);
RegisterSingleInstance(UserManager); RegisterSingleInstance(UserManager);
LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => DirectoryWatchers, FileSystemManager); LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager);
RegisterSingleInstance(LibraryManager); RegisterSingleInstance(LibraryManager);
DirectoryWatchers = new DirectoryWatchers(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager); LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager);
RegisterSingleInstance(DirectoryWatchers); RegisterSingleInstance(LibraryMonitor);
ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, DirectoryWatchers, LogManager, FileSystemManager, ItemRepository); ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ItemRepository);
RegisterSingleInstance(ProviderManager); RegisterSingleInstance(ProviderManager);
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager)); RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
@ -306,7 +306,7 @@ namespace MediaBrowser.ServerApplication
var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer);
RegisterSingleInstance<INewsService>(newsService); RegisterSingleInstance<INewsService>(newsService);
var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, DirectoryWatchers, LibraryManager, ServerConfigurationManager, FileSystemManager); var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, LibraryMonitor, LibraryManager, ServerConfigurationManager, FileSystemManager);
RegisterSingleInstance<IFileOrganizationService>(fileOrganizationService); RegisterSingleInstance<IFileOrganizationService>(fileOrganizationService);
progress.Report(15); progress.Report(15);

Loading…
Cancel
Save