diff --git a/src/NzbDrone.Core/Download/DownloadClientPath.cs b/src/NzbDrone.Core/Download/DownloadClientPath.cs
new file mode 100644
index 000000000..e3891e4a6
--- /dev/null
+++ b/src/NzbDrone.Core/Download/DownloadClientPath.cs
@@ -0,0 +1,8 @@
+namespace NzbDrone.Core.Download
+{
+ public class DownloadClientPath
+ {
+ public int DownloadClientId { get; set; }
+ public string Path { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index e729263de..b88d16322 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -503,6 +503,7 @@
+
@@ -947,7 +948,10 @@
+
+
+
diff --git a/src/NzbDrone.Core/TransferProviders/ITransferProvider.cs b/src/NzbDrone.Core/TransferProviders/ITransferProvider.cs
index 9f35e6476..e06b85d53 100644
--- a/src/NzbDrone.Core/TransferProviders/ITransferProvider.cs
+++ b/src/NzbDrone.Core/TransferProviders/ITransferProvider.cs
@@ -7,14 +7,10 @@ namespace NzbDrone.Core.TransferProviders
{
public interface ITransferProvider : IProvider
{
- // TODO: Perhaps change 'string' to 'DownloadClientPath' struct/class so we're more typesafe.
-
// Whether the TransferProvider is ready to be accessed. (Useful for external transfers that may not have finished yet)
- bool IsAvailable(string downloadClientPath);
- bool IsAvailable(DownloadClientItem item);
+ bool IsAvailable(DownloadClientPath item);
- // Returns a wrapper for the specific download. Optionally we might want to supply a 'tempDir' that's close to the series path, in case the TransferProvider needs an intermediate location.
- IVirtualDiskProvider GetFileSystemWrapper(string downloadClientPath);
- IVirtualDiskProvider GetFileSystemWrapper(DownloadClientItem item);
+ // Returns a wrapper for the specific download. Optionally we might want to supply a 'tempPath' that's close to the series path, in case the TransferProvider needs an intermediate location.
+ IVirtualDiskProvider GetFileSystemWrapper(DownloadClientPath item, string tempPath = null);
}
}
diff --git a/src/NzbDrone.Core/TransferProviders/IVirtualDiskProvider.cs b/src/NzbDrone.Core/TransferProviders/IVirtualDiskProvider.cs
index a5542028c..1b591c78d 100644
--- a/src/NzbDrone.Core/TransferProviders/IVirtualDiskProvider.cs
+++ b/src/NzbDrone.Core/TransferProviders/IVirtualDiskProvider.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
using NzbDrone.Common.Disk;
@@ -16,6 +17,9 @@ namespace NzbDrone.Core.TransferProviders
// Returns recursive list of all files in the 'volume'/'filesystem'/'dataset' (whatever we want to call it).
string[] GetFiles();
+ // Opens a readable stream.
+ Stream OpenFile(string vfsFilePath);
+
// Copies file from the virtual filesystem to the actual one.
TransferTask CopyFile(string vfsSourcePath, string destinationPath);
diff --git a/src/NzbDrone.Core/TransferProviders/Providers/DefaultTransfer.cs b/src/NzbDrone.Core/TransferProviders/Providers/DefaultTransfer.cs
index 83c1cb8e2..98e652cd9 100644
--- a/src/NzbDrone.Core/TransferProviders/Providers/DefaultTransfer.cs
+++ b/src/NzbDrone.Core/TransferProviders/Providers/DefaultTransfer.cs
@@ -1,7 +1,12 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using FluentValidation.Results;
+using NLog;
+using NzbDrone.Common.Disk;
+using NzbDrone.Common.EnsureThat;
+using NzbDrone.Core.Download;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.TransferProviders.Providers
@@ -9,6 +14,19 @@ namespace NzbDrone.Core.TransferProviders.Providers
// Represents a local filesystem transfer.
class DefaultTransfer : TransferProviderBase
{
+ private readonly Logger _logger;
+ private readonly IDiskProvider _diskProvider;
+ private readonly IDiskTransferService _transferService;
+
+ public override string Name => "Default";
+
+ public DefaultTransfer(IDiskTransferService transferService, IDiskProvider diskProvider, Logger logger)
+ {
+ _logger = logger;
+ _diskProvider = diskProvider;
+ _transferService = transferService;
+ }
+
public override IEnumerable DefaultDefinitions
{
get
@@ -23,19 +41,39 @@ namespace NzbDrone.Core.TransferProviders.Providers
};
}
}
- public override string Link
+
+ public override ValidationResult Test()
{
- get { throw new NotImplementedException(); }
+ throw new NotImplementedException();
}
- public override string Name
+ public override bool IsAvailable(DownloadClientPath item)
{
- get { throw new NotImplementedException(); }
+ if (item == null) return false;
+
+ var path = ResolvePath(item);
+
+ return _diskProvider.FolderExists(path) || _diskProvider.FileExists(path);
}
- public override ValidationResult Test()
+ // TODO: Give DirectVirtualDiskProvider the tempPath.
+ public override IVirtualDiskProvider GetFileSystemWrapper(DownloadClientPath item, string tempPath = null)
{
- throw new NotImplementedException();
+ var path = ResolvePath(item);
+
+ if (_diskProvider.FolderExists(path) || _diskProvider.FileExists(path))
+ {
+ // Expose a virtual filesystem with only that directory/file in it.
+ // This allows the caller to delete the directory if desired, but not it's siblings.
+ return new DirectVirtualDiskProvider(_diskProvider, _transferService, Path.GetDirectoryName(path), path);
+ }
+
+ return new EmptyVirtualDiskProvider();
+ }
+
+ protected string ResolvePath(DownloadClientPath path)
+ {
+ return path.Path;
}
}
}
diff --git a/src/NzbDrone.Core/TransferProviders/Providers/DirectVirtualDiskProvider.cs b/src/NzbDrone.Core/TransferProviders/Providers/DirectVirtualDiskProvider.cs
new file mode 100644
index 000000000..b05695361
--- /dev/null
+++ b/src/NzbDrone.Core/TransferProviders/Providers/DirectVirtualDiskProvider.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NzbDrone.Common.Disk;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Common.Timeline;
+using NzbDrone.Common.TPL;
+
+namespace NzbDrone.Core.TransferProviders.Providers
+{
+ public class DirectVirtualDiskProvider : IVirtualDiskProvider
+ {
+ private readonly IDiskProvider _diskProvider;
+ private readonly IDiskTransferService _transferService;
+ private readonly string _rootFolder;
+ private readonly List _items;
+
+ public bool SupportStreaming => true;
+
+ public DirectVirtualDiskProvider(IDiskProvider diskProvider, IDiskTransferService transferService, string rootFolder, params string[] items)
+ {
+ _diskProvider = diskProvider;
+ _transferService = transferService;
+ _rootFolder = rootFolder;
+ _items = items.ToList();
+ }
+
+ public string[] GetFiles()
+ {
+ return _items.SelectMany(GetFiles).Select(_rootFolder.GetRelativePath).ToArray();
+ }
+
+ private string[] GetFiles(string sourcePath)
+ {
+ if (_diskProvider.FileExists(sourcePath))
+ {
+ return new [] { sourcePath };
+ }
+ else
+ {
+ return _diskProvider.GetFiles(sourcePath, SearchOption.AllDirectories);
+ }
+ }
+
+ public TransferTask MoveFile(string vfsSourcePath, string destinationPath)
+ {
+ return TransferFile(vfsSourcePath, destinationPath, TransferMode.Move);
+ }
+
+ public TransferTask CopyFile(string vfsSourcePath, string destinationPath)
+ {
+ return TransferFile(vfsSourcePath, destinationPath, TransferMode.Copy);
+ }
+
+ private TransferTask TransferFile(string vfsSourcePath, string destinationPath, TransferMode mode)
+ {
+ var sourcePath = ResolveVirtualPath(vfsSourcePath);
+
+ var fileSize = _diskProvider.GetFileSize(sourcePath);
+ var progress = new TimelineContext($"{mode} {Path.GetFileName(sourcePath)}", 0, fileSize);
+ var task = Task.Factory.StartNew(() =>
+ {
+ progress.UpdateState(TimelineState.Started);
+ _transferService.TransferFile(sourcePath, destinationPath, mode);
+ if (mode == TransferMode.Move && _items.Contains(vfsSourcePath))
+ {
+ // If it was moved, then remove it from the list.
+ _items.Remove(vfsSourcePath);
+ }
+ progress.FinishProgress();
+ });
+
+ return new TransferTask(progress, task);
+ }
+
+ public Stream OpenFile(string vfsFilePath)
+ {
+ var sourcePath = ResolveVirtualPath(vfsFilePath);
+
+ return _diskProvider.OpenReadStream(sourcePath);
+ }
+
+ private string ResolveVirtualPath(string virtualPath)
+ {
+ if (Path.IsPathRooted(virtualPath))
+ {
+ throw new InvalidOperationException("Path not valid in the virtual filesystem");
+ }
+
+ var basePath = virtualPath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)[0];
+
+ if (!_items.Contains(basePath))
+ {
+ throw new InvalidOperationException("Path not valid in the virtual filesystem");
+ }
+
+ return Path.Combine(_rootFolder, virtualPath);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/TransferProviders/Providers/Dummy.cs b/src/NzbDrone.Core/TransferProviders/Providers/Dummy.cs
deleted file mode 100644
index 40aaa8538..000000000
--- a/src/NzbDrone.Core/TransferProviders/Providers/Dummy.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using FluentValidation.Results;
-using NzbDrone.Core.ThingiProvider;
-
-namespace NzbDrone.Core.TransferProviders.Providers
-{
- // Marks the files are permanently unavailable. Perhaps useful in fire-and-forget.
- class Dummy : TransferProviderBase
- {
- public override string Link
- {
- get { throw new NotImplementedException(); }
- }
-
- public override string Name
- {
- get { throw new NotImplementedException(); }
- }
-
- public override ValidationResult Test()
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/src/NzbDrone.Core/TransferProviders/Providers/EmptyVirtualDiskProvider.cs b/src/NzbDrone.Core/TransferProviders/Providers/EmptyVirtualDiskProvider.cs
new file mode 100644
index 000000000..40c32e5b5
--- /dev/null
+++ b/src/NzbDrone.Core/TransferProviders/Providers/EmptyVirtualDiskProvider.cs
@@ -0,0 +1,32 @@
+using System;
+using System.IO;
+using System.Linq;
+
+namespace NzbDrone.Core.TransferProviders.Providers
+{
+ public class EmptyVirtualDiskProvider : IVirtualDiskProvider
+ {
+ public bool SupportStreaming => true;
+
+
+ public string[] GetFiles()
+ {
+ return new string[0];
+ }
+
+ public TransferTask MoveFile(string vfsSourcePath, string destinationPath)
+ {
+ throw new FileNotFoundException("File not found in virtual filesystem", vfsSourcePath);
+ }
+
+ public TransferTask CopyFile(string vfsSourcePath, string destinationPath)
+ {
+ throw new FileNotFoundException("File not found in virtual filesystem", vfsSourcePath);
+ }
+
+ public Stream OpenFile(string vfsFilePath)
+ {
+ throw new FileNotFoundException("File not found in virtual filesystem", vfsFilePath);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/TransferProviders/Providers/MountTransfer.cs b/src/NzbDrone.Core/TransferProviders/Providers/MountTransfer.cs
index 38a7252d0..d2b1880aa 100644
--- a/src/NzbDrone.Core/TransferProviders/Providers/MountTransfer.cs
+++ b/src/NzbDrone.Core/TransferProviders/Providers/MountTransfer.cs
@@ -1,6 +1,10 @@
using System;
+using System.IO;
using System.Linq;
using FluentValidation.Results;
+using NLog;
+using NzbDrone.Common.Disk;
+using NzbDrone.Core.Download;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
@@ -21,18 +25,51 @@ namespace NzbDrone.Core.TransferProviders.Providers
public class MountTransfer : TransferProviderBase
{
- public override string Link
+ private readonly Logger _logger;
+ private readonly IDiskProvider _diskProvider;
+ private readonly IDiskTransferService _transferService;
+
+ public override string Name => "Mount";
+
+ public MountTransfer(IDiskTransferService transferService, IDiskProvider diskProvider, Logger logger)
{
- get { throw new NotImplementedException(); }
+ _logger = logger;
+ _diskProvider = diskProvider;
+ _transferService = transferService;
}
- public override string Name
+ public override ValidationResult Test()
{
- get { throw new NotImplementedException(); }
+ throw new NotImplementedException();
}
- public override ValidationResult Test()
+ public override bool IsAvailable(DownloadClientPath item)
+ {
+ if (item == null) return false;
+
+ var path = ResolvePath(item);
+
+ return _diskProvider.FolderExists(path) || _diskProvider.FileExists(path);
+ }
+
+ // TODO: Give MountVirtualDiskProvider the tempPath.
+ public override IVirtualDiskProvider GetFileSystemWrapper(DownloadClientPath item, string tempPath = null)
+ {
+ var path = ResolvePath(item);
+
+ if (_diskProvider.FolderExists(path) || _diskProvider.FileExists(path))
+ {
+ // Expose a virtual filesystem with only that directory/file in it.
+ // This allows the caller to delete the directory if desired, but not it's siblings.
+ return new MountVirtualDiskProvider(_diskProvider, _transferService, Path.GetDirectoryName(path), path);
+ }
+
+ return new EmptyVirtualDiskProvider();
+ }
+
+ protected string ResolvePath(DownloadClientPath path)
{
+ // Same logic as RemotePathMapping service.
throw new NotImplementedException();
}
}
diff --git a/src/NzbDrone.Core/TransferProviders/Providers/MountVirtualDiskProvider.cs b/src/NzbDrone.Core/TransferProviders/Providers/MountVirtualDiskProvider.cs
new file mode 100644
index 000000000..257f3be21
--- /dev/null
+++ b/src/NzbDrone.Core/TransferProviders/Providers/MountVirtualDiskProvider.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NzbDrone.Common.Disk;
+
+namespace NzbDrone.Core.TransferProviders.Providers
+{
+ // Empty wrapper, it would server in dealing with stuff being slower and remote mounts potentially being unavailable temporarily.
+ // Ideally it should wrap a DirectVirtualDiskProvider instance, rather than inheriting from it.
+ public class MountVirtualDiskProvider : DirectVirtualDiskProvider
+ {
+ public MountVirtualDiskProvider(IDiskProvider diskProvider, IDiskTransferService transferService, string rootFolder, params string[] items)
+ : base(diskProvider, transferService, rootFolder, items)
+ {
+
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/TransferProviders/TransferProviderBase.cs b/src/NzbDrone.Core/TransferProviders/TransferProviderBase.cs
index 3155ba4cf..fea5b8900 100644
--- a/src/NzbDrone.Core/TransferProviders/TransferProviderBase.cs
+++ b/src/NzbDrone.Core/TransferProviders/TransferProviderBase.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
+using NzbDrone.Core.Download;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.TransferProviders
@@ -19,8 +20,10 @@ namespace NzbDrone.Core.TransferProviders
public ProviderDefinition Definition { get; set; }
public abstract ValidationResult Test();
- public abstract string Link { get; }
-
public virtual object RequestAction(string action, IDictionary query) { return null; }
+
+ public abstract bool IsAvailable(DownloadClientPath item);
+
+ public abstract IVirtualDiskProvider GetFileSystemWrapper(DownloadClientPath item, string tempPath = null);
}
}
diff --git a/src/NzbDrone.Core/TransferProviders/TransferTask.cs b/src/NzbDrone.Core/TransferProviders/TransferTask.cs
index f306195d1..2fecbb0c6 100644
--- a/src/NzbDrone.Core/TransferProviders/TransferTask.cs
+++ b/src/NzbDrone.Core/TransferProviders/TransferTask.cs
@@ -2,13 +2,22 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
+using NzbDrone.Common.Timeline;
namespace NzbDrone.Core.TransferProviders
{
public class TransferTask
{
- // TODO: Progress reporting
+ public ITimelineContext Timeline { get; private set; }
- // TODO: Async task or waitable object so Importing can handle.
+ // Async task that is completed once the Transfer has finished or ended in failure. (Do not rely on ProgressReporter for finished detection)
+ public Task CompletionTask { get; private set; }
+
+ public TransferTask(ITimelineContext timeline, Task completionTask)
+ {
+ Timeline = timeline;
+ CompletionTask = completionTask.ContinueWith(t => Timeline.FinishProgress());
+ }
}
}