Files
AZKi/AZKiServer/Services/FileScannerService.cs

137 lines
3.8 KiB
C#

using AZKiServer.Models;
using FFMpegCore;
using MaybeError;
using MaybeError.Errors;
using SixLabors.ImageSharp;
namespace AZKiServer.Services;
public class FileScannerService(MediaService mediaService, IConfiguration config, ILogger<FileScannerService> logger) : IHostedService, IDisposable
{
private Timer? _timer;
private bool _isRunning;
public void Dispose()
{
_timer?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
var path = config["SCAN_LOCATION"];
if (string.IsNullOrWhiteSpace(path))
return Task.CompletedTask;
_timer = new Timer((_) =>
{
if (_isRunning)
return;
_isRunning = true;
ScanFilesAsync(path).Wait();
_isRunning = false;
}, null, TimeSpan.FromMinutes(0), TimeSpan.FromHours(1));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Dispose();
return Task.CompletedTask;
}
private async Task ScanFilesAsync(string path, CancellationToken cancellationToken = default)
{
logger.LogInformation("Scanning Files");
try
{
var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
var existingFiles = await mediaService.GetExistingFilePathsAsync(cancellationToken);
var entries = new List<MediaEntry>();
var upgradeEntries = new List<MediaEntry>();
foreach (var filePath in files)
{
if (cancellationToken.IsCancellationRequested)
break;
var relativePath = Path.GetRelativePath(path, filePath);
if (relativePath[0] == '.') //Ignore hidden folders
continue;
var isUpgrade = false;
if (existingFiles.TryGetValue(relativePath, out var version))
{
if(version < MediaEntry.CUR_VERSION)
continue;
isUpgrade = true;
}
var metadata = ReadMetadata(filePath);
if(metadata.HasError)
{
logger.LogError(metadata.Error.GetException(), $"Failed to get metadata for file: {filePath}");
continue;
}
var entry = MediaEntry.Parse(relativePath, metadata);
if(entry.HasError)
{
logger.LogError(entry.Error.GetException(), "Failed to parse file data");
continue;
}
if(isUpgrade)
upgradeEntries.Add(entry);
else
entries.Add(entry);
}
cancellationToken.ThrowIfCancellationRequested();
if(entries.Count > 0) {
await mediaService.AddMediaBulkAsync(entries, cancellationToken);
logger.LogInformation("Added {count} file entries", entries.Count);
}
if (upgradeEntries.Count > 0)
{
await mediaService.DeleteAllEntriesAsync(upgradeEntries.Select(e => e.Filepath), cancellationToken);
await mediaService.AddMediaBulkAsync(upgradeEntries, cancellationToken);
logger.LogInformation("Upgraded {count} file entries", entries.Count);
}
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to read directory contents");
}
}
private static Maybe<MediaMetadata> ReadMetadata(string filePath)
{
var ext = Path.GetExtension(filePath);
return ext switch
{
".jpg" or ".png" or ".jpeg" => ReadImageMetadata(filePath),
".mp4" => ReadVideoMetadata(filePath),
_ => throw new NotSupportedException($"Files of type {ext} are not supported")
};
}
private static Maybe<MediaMetadata> ReadImageMetadata(string filePath)
{
try
{
var info = Image.Identify(filePath);
return new MediaMetadata(0, info.Height, info.Width);
}
catch (Exception ex)
{
return ex;
}
}
private static Maybe<MediaMetadata> ReadVideoMetadata(string filePath)
{
var info = FFProbe.Analyse(filePath);
if (info.PrimaryVideoStream == null)
return new Error($"Could not find a primirary video stream in file.");
return new MediaMetadata((int)info.Duration.TotalSeconds, info.PrimaryVideoStream.Height, info.PrimaryVideoStream.Width);
}
}