Add torrent client integration and downloads management: introduce qbittorrent service in Docker setup, implement downloads endpoint in API, and enhance media storage to handle imported files. Update frontend routes and translations for downloads management.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Media;
|
||||
using TeleWave.Domain.Media;
|
||||
|
||||
namespace TeleWave.Infrastructure.Media;
|
||||
@@ -21,6 +22,41 @@ public sealed class FileSystemMediaStorage(MediaPathResolver paths) : IMediaStor
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<DownloadEntry> ListDownloads()
|
||||
{
|
||||
if (!Directory.Exists(paths.DownloadsDir))
|
||||
return [];
|
||||
|
||||
var result = new List<DownloadEntry>();
|
||||
foreach (var file in Directory.EnumerateFiles(paths.DownloadsDir, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
var name = Path.GetFileName(file);
|
||||
if (!MediaFormats.IsAllowed(name))
|
||||
continue;
|
||||
// Пропускаем недокачанное: маркеры qBittorrent (.!qB) и каталог незавершённых.
|
||||
if (name.EndsWith(".!qb", StringComparison.OrdinalIgnoreCase)
|
||||
|| file.Contains($"{Path.DirectorySeparatorChar}incomplete{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
long size;
|
||||
try
|
||||
{
|
||||
size = new FileInfo(file).Length;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (size <= 0)
|
||||
continue;
|
||||
|
||||
var relative = Path.GetRelativePath(paths.DownloadsDir, file).Replace('\\', '/');
|
||||
result.Add(new DownloadEntry(relative, name, size));
|
||||
}
|
||||
|
||||
return result.OrderBy(e => e.RelativePath, StringComparer.Ordinal).ToList();
|
||||
}
|
||||
|
||||
public async Task<string> SaveUploadAsync(
|
||||
Stream content,
|
||||
string extension,
|
||||
@@ -58,16 +94,24 @@ public sealed class FileSystemMediaStorage(MediaPathResolver paths) : IMediaStor
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var sourcePath = source == MediaSource.Inbox
|
||||
? paths.InboxPath(sourceToken)
|
||||
: paths.UploadPath(sourceToken);
|
||||
var sourcePath = source switch
|
||||
{
|
||||
MediaSource.Inbox => paths.InboxPath(sourceToken),
|
||||
MediaSource.Download => paths.DownloadPath(sourceToken),
|
||||
_ => paths.UploadPath(sourceToken),
|
||||
};
|
||||
|
||||
if (!File.Exists(sourcePath))
|
||||
throw new FileNotFoundException("Исходный файл не найден в хранилище.", sourcePath);
|
||||
|
||||
Directory.CreateDirectory(paths.OriginalsDir);
|
||||
var destination = paths.OriginalPath(assetId, extension);
|
||||
File.Move(sourcePath, destination, overwrite: true);
|
||||
|
||||
// Download копируем (торрент продолжает сидировать); остальное переносим.
|
||||
if (source == MediaSource.Download)
|
||||
File.Copy(sourcePath, destination, overwrite: true);
|
||||
else
|
||||
File.Move(sourcePath, destination, overwrite: true);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,14 @@ public sealed class MediaPathResolver
|
||||
UploadsDir = Path.Combine(_root, "uploads");
|
||||
OriginalsDir = Path.Combine(_root, "originals");
|
||||
AssetsDir = Path.Combine(_root, "assets");
|
||||
DownloadsDir = Path.Combine(_root, "downloads");
|
||||
}
|
||||
|
||||
public string InboxDir { get; }
|
||||
public string UploadsDir { get; }
|
||||
public string OriginalsDir { get; }
|
||||
public string AssetsDir { get; }
|
||||
public string DownloadsDir { get; }
|
||||
|
||||
public void EnsureDirectories()
|
||||
{
|
||||
@@ -30,6 +32,7 @@ public sealed class MediaPathResolver
|
||||
Directory.CreateDirectory(UploadsDir);
|
||||
Directory.CreateDirectory(OriginalsDir);
|
||||
Directory.CreateDirectory(AssetsDir);
|
||||
Directory.CreateDirectory(DownloadsDir);
|
||||
}
|
||||
|
||||
public string OriginalPath(Guid assetId, string extension) =>
|
||||
@@ -55,6 +58,10 @@ public sealed class MediaPathResolver
|
||||
public string InboxPath(string fileName) =>
|
||||
EnsureWithin(InboxDir, Path.Combine(InboxDir, fileName));
|
||||
|
||||
/// <summary>Резолвит относительный путь внутри downloads/ (может содержать подкаталоги сезона).</summary>
|
||||
public string DownloadPath(string relativePath) =>
|
||||
EnsureWithin(DownloadsDir, Path.Combine(DownloadsDir, relativePath));
|
||||
|
||||
private string EnsureWithinRoot(string candidate) => EnsureWithin(_root, candidate);
|
||||
|
||||
private static string EnsureWithin(string baseDir, string candidate)
|
||||
|
||||
Reference in New Issue
Block a user