272 lines
10 KiB
C#
272 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Hosting;
|
|
using TransmissionRssManager.Core;
|
|
|
|
namespace TransmissionRssManager.Services
|
|
{
|
|
public class PostProcessor : IPostProcessor
|
|
{
|
|
private readonly ILogger<PostProcessor> _logger;
|
|
private readonly IConfigService _configService;
|
|
private readonly ITransmissionClient _transmissionClient;
|
|
|
|
public PostProcessor(
|
|
ILogger<PostProcessor> logger,
|
|
IConfigService configService,
|
|
ITransmissionClient transmissionClient)
|
|
{
|
|
_logger = logger;
|
|
_configService = configService;
|
|
_transmissionClient = transmissionClient;
|
|
}
|
|
|
|
public async Task ProcessCompletedDownloadsAsync(CancellationToken cancellationToken)
|
|
{
|
|
var config = _configService.GetConfiguration();
|
|
|
|
if (!config.PostProcessing.Enabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_logger.LogInformation("Processing completed downloads");
|
|
|
|
var torrents = await _transmissionClient.GetTorrentsAsync();
|
|
var completedTorrents = torrents.Where(t => t.IsFinished).ToList();
|
|
|
|
foreach (var torrent in completedTorrents)
|
|
{
|
|
if (cancellationToken.IsCancellationRequested)
|
|
{
|
|
_logger.LogInformation("Post-processing cancelled");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
await ProcessTorrentAsync(torrent);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error processing torrent: {torrent.Name}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task ProcessTorrentAsync(TorrentInfo torrent)
|
|
{
|
|
_logger.LogInformation($"Processing completed torrent: {torrent.Name}");
|
|
|
|
var config = _configService.GetConfiguration();
|
|
var downloadDir = torrent.DownloadDir;
|
|
var torrentPath = Path.Combine(downloadDir, torrent.Name);
|
|
|
|
// Check if the file/directory exists
|
|
if (!Directory.Exists(torrentPath) && !File.Exists(torrentPath))
|
|
{
|
|
_logger.LogWarning($"Downloaded path not found: {torrentPath}");
|
|
return;
|
|
}
|
|
|
|
// Handle archives if enabled
|
|
if (config.PostProcessing.ExtractArchives && IsArchive(torrentPath))
|
|
{
|
|
await ExtractArchiveAsync(torrentPath, downloadDir);
|
|
}
|
|
|
|
// Organize media files if enabled
|
|
if (config.PostProcessing.OrganizeMedia)
|
|
{
|
|
await OrganizeMediaAsync(torrentPath, config.MediaLibraryPath);
|
|
}
|
|
}
|
|
|
|
private bool IsArchive(string path)
|
|
{
|
|
if (!File.Exists(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var extension = Path.GetExtension(path).ToLowerInvariant();
|
|
return extension == ".rar" || extension == ".zip" || extension == ".7z";
|
|
}
|
|
|
|
private async Task ExtractArchiveAsync(string archivePath, string outputDir)
|
|
{
|
|
_logger.LogInformation($"Extracting archive: {archivePath}");
|
|
|
|
try
|
|
{
|
|
var extension = Path.GetExtension(archivePath).ToLowerInvariant();
|
|
var extractDir = Path.Combine(outputDir, Path.GetFileNameWithoutExtension(archivePath));
|
|
|
|
// Create extraction directory if it doesn't exist
|
|
if (!Directory.Exists(extractDir))
|
|
{
|
|
Directory.CreateDirectory(extractDir);
|
|
}
|
|
|
|
var processStartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = extension switch
|
|
{
|
|
".rar" => "unrar",
|
|
".zip" => "unzip",
|
|
".7z" => "7z",
|
|
_ => throw new Exception($"Unsupported archive format: {extension}")
|
|
},
|
|
Arguments = extension switch
|
|
{
|
|
".rar" => $"x -o+ \"{archivePath}\" \"{extractDir}\"",
|
|
".zip" => $"-o \"{archivePath}\" -d \"{extractDir}\"",
|
|
".7z" => $"x \"{archivePath}\" -o\"{extractDir}\"",
|
|
_ => throw new Exception($"Unsupported archive format: {extension}")
|
|
},
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
};
|
|
|
|
using var process = new Process
|
|
{
|
|
StartInfo = processStartInfo
|
|
};
|
|
|
|
process.Start();
|
|
await process.WaitForExitAsync();
|
|
|
|
if (process.ExitCode != 0)
|
|
{
|
|
var error = await process.StandardError.ReadToEndAsync();
|
|
_logger.LogError($"Error extracting archive: {error}");
|
|
return;
|
|
}
|
|
|
|
_logger.LogInformation($"Archive extracted to: {extractDir}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error extracting archive: {archivePath}");
|
|
}
|
|
}
|
|
|
|
private async Task OrganizeMediaAsync(string path, string mediaLibraryPath)
|
|
{
|
|
_logger.LogInformation($"Organizing media: {path}");
|
|
|
|
var config = _configService.GetConfiguration();
|
|
var mediaExtensions = config.PostProcessing.MediaExtensions;
|
|
|
|
// Ensure media library path exists
|
|
if (!Directory.Exists(mediaLibraryPath))
|
|
{
|
|
Directory.CreateDirectory(mediaLibraryPath);
|
|
}
|
|
|
|
try
|
|
{
|
|
if (File.Exists(path))
|
|
{
|
|
// Single file
|
|
var extension = Path.GetExtension(path).ToLowerInvariant();
|
|
if (mediaExtensions.Contains(extension))
|
|
{
|
|
await CopyFileToMediaLibraryAsync(path, mediaLibraryPath);
|
|
}
|
|
}
|
|
else if (Directory.Exists(path))
|
|
{
|
|
// Directory - find all media files recursively
|
|
var mediaFiles = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)
|
|
.Where(f => mediaExtensions.Contains(Path.GetExtension(f).ToLowerInvariant()))
|
|
.ToList();
|
|
|
|
foreach (var mediaFile in mediaFiles)
|
|
{
|
|
await CopyFileToMediaLibraryAsync(mediaFile, mediaLibraryPath);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error organizing media: {path}");
|
|
}
|
|
}
|
|
|
|
private async Task CopyFileToMediaLibraryAsync(string filePath, string mediaLibraryPath)
|
|
{
|
|
var fileName = Path.GetFileName(filePath);
|
|
var destinationPath = Path.Combine(mediaLibraryPath, fileName);
|
|
|
|
// If destination file already exists, add a unique identifier
|
|
if (File.Exists(destinationPath))
|
|
{
|
|
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
|
|
var extension = Path.GetExtension(fileName);
|
|
var uniqueId = Guid.NewGuid().ToString().Substring(0, 8);
|
|
|
|
destinationPath = Path.Combine(mediaLibraryPath, $"{fileNameWithoutExt}_{uniqueId}{extension}");
|
|
}
|
|
|
|
_logger.LogInformation($"Copying media file to library: {destinationPath}");
|
|
|
|
try
|
|
{
|
|
using var sourceStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);
|
|
using var destinationStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true);
|
|
|
|
await sourceStream.CopyToAsync(destinationStream);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error copying file to media library: {filePath}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public class PostProcessingBackgroundService : BackgroundService
|
|
{
|
|
private readonly ILogger<PostProcessingBackgroundService> _logger;
|
|
private readonly IPostProcessor _postProcessor;
|
|
private readonly IConfigService _configService;
|
|
|
|
public PostProcessingBackgroundService(
|
|
ILogger<PostProcessingBackgroundService> logger,
|
|
IPostProcessor postProcessor,
|
|
IConfigService configService)
|
|
{
|
|
_logger = logger;
|
|
_postProcessor = postProcessor;
|
|
_configService = configService;
|
|
}
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
_logger.LogInformation("Post-processing background service started");
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
await _postProcessor.ProcessCompletedDownloadsAsync(stoppingToken);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error processing completed downloads");
|
|
}
|
|
|
|
// Check every 5 minutes
|
|
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
|
}
|
|
}
|
|
}
|
|
} |