You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Lidarr/src/NzbDrone.Core/Download/Clients/Blackhole/ScanWatchFolder.cs

197 lines
6.9 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Crypto;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Organizer;
namespace NzbDrone.Core.Download.Clients.Blackhole
{
public interface IScanWatchFolder
{
IEnumerable<WatchFolderItem> GetItems(string watchFolder, TimeSpan waitPeriod);
}
public class ScanWatchFolder : IScanWatchFolder
{
private readonly Logger _logger;
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
private readonly ICached<Dictionary<string, WatchFolderItem>> _watchFolderItemCache;
public ScanWatchFolder(ICacheManager cacheManager, IDiskScanService diskScanService, IDiskProvider diskProvider, Logger logger)
{
_logger = logger;
_diskProvider = diskProvider;
_diskScanService = diskScanService;
_watchFolderItemCache = cacheManager.GetCache<Dictionary<string, WatchFolderItem>>(GetType());
}
public IEnumerable<WatchFolderItem> GetItems(string watchFolder, TimeSpan waitPeriod)
{
var newWatchItems = new Dictionary<string, WatchFolderItem>();
var lastWatchItems = _watchFolderItemCache.Get(watchFolder, () => newWatchItems);
foreach (var newWatchItem in GetDownloadItems(watchFolder, lastWatchItems, waitPeriod))
{
newWatchItems[newWatchItem.DownloadId] = newWatchItem;
}
_watchFolderItemCache.Set(watchFolder, newWatchItems, TimeSpan.FromMinutes(5));
return newWatchItems.Values;
}
private IEnumerable<WatchFolderItem> GetDownloadItems(string watchFolder, Dictionary<string, WatchFolderItem> lastWatchItems, TimeSpan waitPeriod)
{
foreach (var folder in _diskScanService.FilterPaths(watchFolder, _diskProvider.GetDirectories(watchFolder)))
{
var title = FileNameBuilder.CleanFileName(Path.GetFileName(folder));
var newWatchItem = new WatchFolderItem
{
DownloadId = Path.GetFileName(folder) + "_" + _diskProvider.FolderGetCreationTime(folder).Ticks,
Title = title,
OutputPath = new OsPath(folder),
Status = DownloadItemStatus.Completed,
RemainingTime = TimeSpan.Zero
};
var oldWatchItem = lastWatchItems.GetValueOrDefault(newWatchItem.DownloadId);
if (PreCheckWatchItemExpiry(newWatchItem, oldWatchItem))
{
var files = _diskProvider.GetFiles(folder, true);
newWatchItem.TotalSize = files.Select(_diskProvider.GetFileSize).Sum();
newWatchItem.Hash = GetHash(folder, files);
if (files.Any(_diskProvider.IsFileLocked))
{
newWatchItem.Status = DownloadItemStatus.Downloading;
newWatchItem.RemainingTime = null;
}
UpdateWatchItemExpiry(newWatchItem, oldWatchItem, waitPeriod);
}
yield return newWatchItem;
}
foreach (var audioFile in _diskScanService.FilterFiles(watchFolder, _diskScanService.GetAudioFiles(watchFolder, false)))
{
var title = FileNameBuilder.CleanFileName(audioFile.Name);
var newWatchItem = new WatchFolderItem
{
DownloadId = audioFile.Name + "_" + audioFile.LastWriteTimeUtc.Ticks,
Title = title,
OutputPath = new OsPath(audioFile.FullName),
Status = DownloadItemStatus.Completed,
RemainingTime = TimeSpan.Zero
};
var oldWatchItem = lastWatchItems.GetValueOrDefault(newWatchItem.DownloadId);
if (PreCheckWatchItemExpiry(newWatchItem, oldWatchItem))
{
newWatchItem.TotalSize = audioFile.Length;
newWatchItem.Hash = GetHash(audioFile.FullName);
if (_diskProvider.IsFileLocked(audioFile.FullName))
{
newWatchItem.Status = DownloadItemStatus.Downloading;
}
UpdateWatchItemExpiry(newWatchItem, oldWatchItem, waitPeriod);
}
yield return newWatchItem;
}
}
private static bool PreCheckWatchItemExpiry(WatchFolderItem newWatchItem, WatchFolderItem oldWatchItem)
{
if (oldWatchItem == null || oldWatchItem.LastChanged.AddHours(1) > DateTime.UtcNow)
{
return true;
}
newWatchItem.TotalSize = oldWatchItem.TotalSize;
newWatchItem.Hash = oldWatchItem.Hash;
return false;
}
private static void UpdateWatchItemExpiry(WatchFolderItem newWatchItem, WatchFolderItem oldWatchItem, TimeSpan waitPeriod)
{
if (oldWatchItem != null && newWatchItem.Hash == oldWatchItem.Hash)
{
newWatchItem.LastChanged = oldWatchItem.LastChanged;
}
else
{
newWatchItem.LastChanged = DateTime.UtcNow;
}
var remainingTime = waitPeriod - (DateTime.UtcNow - newWatchItem.LastChanged);
if (remainingTime > TimeSpan.Zero)
{
newWatchItem.RemainingTime = remainingTime;
newWatchItem.Status = DownloadItemStatus.Downloading;
}
}
private string GetHash(string folder, IEnumerable<string> files)
{
var data = new StringBuilder();
data.Append(folder);
try
{
data.Append(_diskProvider.FolderGetLastWrite(folder).ToBinary());
}
catch (Exception ex)
{
_logger.Trace(ex, "Ignored hashing error during scan for {0}", folder);
}
foreach (var file in files.OrderBy(v => v))
{
data.Append(GetHash(file));
}
return HashConverter.GetHash(data.ToString()).ToHexString();
}
private string GetHash(string file)
{
var data = new StringBuilder();
data.Append(file);
try
{
data.Append(_diskProvider.FileGetLastWrite(file).ToBinary());
data.Append(_diskProvider.GetFileSize(file));
}
catch (Exception ex)
{
_logger.Trace(ex, "Ignored hashing error during scan for {0}", file);
}
return HashConverter.GetHash(data.ToString()).ToHexString();
}
}
}