From 1d451aac8213bcf045ced647f342428a289d02fc Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 6 Sep 2022 22:32:22 +0100 Subject: [PATCH] Delete expired files --- VoidCat/Controllers/Admin/AdminController.cs | 4 +- VoidCat/Controllers/DownloadController.cs | 4 +- VoidCat/Controllers/UploadController.cs | 4 +- VoidCat/Controllers/UserController.cs | 5 +- .../Services/Abstractions/IFileInfoManager.cs | 38 -------------- .../Services/Background/DeleteExpiredFiles.cs | 52 +++++++++++++++++++ .../Background/DeleteUnverifiedAccounts.cs | 2 +- VoidCat/Services/Files/FileInfoManager.cs | 31 ++++++++--- VoidCat/Services/Files/FileStorageStartup.cs | 4 +- .../Files/LocalDiskFileMetadataStore.cs | 2 +- .../Services/Files/LocalDiskFileStorage.cs | 5 +- .../Files/PostgresFileMetadataStore.cs | 4 +- VoidCat/Services/Files/S3FileMetadataStore.cs | 2 +- VoidCat/Services/Files/S3FileStore.cs | 4 +- VoidCat/VoidStartup.cs | 1 + VoidCat/spa/src/ApiKeyList.js | 7 ++- VoidCat/spa/src/FileEdit.js | 29 +++++++++-- VoidCat/spa/src/index.css | 2 +- 18 files changed, 128 insertions(+), 72 deletions(-) delete mode 100644 VoidCat/Services/Abstractions/IFileInfoManager.cs create mode 100644 VoidCat/Services/Background/DeleteExpiredFiles.cs diff --git a/VoidCat/Controllers/Admin/AdminController.cs b/VoidCat/Controllers/Admin/AdminController.cs index 23618b5..fe945f1 100644 --- a/VoidCat/Controllers/Admin/AdminController.cs +++ b/VoidCat/Controllers/Admin/AdminController.cs @@ -12,11 +12,11 @@ public class AdminController : Controller { private readonly FileStoreFactory _fileStore; private readonly IFileMetadataStore _fileMetadata; - private readonly IFileInfoManager _fileInfo; + private readonly FileInfoManager _fileInfo; private readonly IUserStore _userStore; private readonly IUserUploadsStore _userUploads; - public AdminController(FileStoreFactory fileStore, IUserStore userStore, IFileInfoManager fileInfo, + public AdminController(FileStoreFactory fileStore, IUserStore userStore, FileInfoManager fileInfo, IFileMetadataStore fileMetadata, IUserUploadsStore userUploads) { _fileStore = fileStore; diff --git a/VoidCat/Controllers/DownloadController.cs b/VoidCat/Controllers/DownloadController.cs index 4e464d3..1806007 100644 --- a/VoidCat/Controllers/DownloadController.cs +++ b/VoidCat/Controllers/DownloadController.cs @@ -11,11 +11,11 @@ namespace VoidCat.Controllers; public class DownloadController : Controller { private readonly FileStoreFactory _storage; - private readonly IFileInfoManager _fileInfo; + private readonly FileInfoManager _fileInfo; private readonly IPaywallOrderStore _paywallOrders; private readonly ILogger _logger; - public DownloadController(FileStoreFactory storage, ILogger logger, IFileInfoManager fileInfo, + public DownloadController(FileStoreFactory storage, ILogger logger, FileInfoManager fileInfo, IPaywallOrderStore paywall) { _storage = storage; diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs index 9bf75ba..d794bec 100644 --- a/VoidCat/Controllers/UploadController.cs +++ b/VoidCat/Controllers/UploadController.cs @@ -17,14 +17,14 @@ namespace VoidCat.Controllers private readonly IFileMetadataStore _metadata; private readonly IPaywallStore _paywall; private readonly IPaywallFactory _paywallFactory; - private readonly IFileInfoManager _fileInfo; + private readonly FileInfoManager _fileInfo; private readonly IUserUploadsStore _userUploads; private readonly IUserStore _userStore; private readonly ITimeSeriesStatsReporter _timeSeriesStats; private readonly VoidSettings _settings; public UploadController(FileStoreFactory storage, IFileMetadataStore metadata, IPaywallStore paywall, - IPaywallFactory paywallFactory, IFileInfoManager fileInfo, IUserUploadsStore userUploads, + IPaywallFactory paywallFactory, FileInfoManager fileInfo, IUserUploadsStore userUploads, ITimeSeriesStatsReporter timeSeriesStats, IUserStore userStore, VoidSettings settings) { _storage = storage; diff --git a/VoidCat/Controllers/UserController.cs b/VoidCat/Controllers/UserController.cs index 2387522..e5f7cb8 100644 --- a/VoidCat/Controllers/UserController.cs +++ b/VoidCat/Controllers/UserController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using VoidCat.Model; using VoidCat.Services.Abstractions; +using VoidCat.Services.Files; namespace VoidCat.Controllers; @@ -10,10 +11,10 @@ public class UserController : Controller private readonly IUserStore _store; private readonly IUserUploadsStore _userUploads; private readonly IEmailVerification _emailVerification; - private readonly IFileInfoManager _fileInfoManager; + private readonly FileInfoManager _fileInfoManager; public UserController(IUserStore store, IUserUploadsStore userUploads, IEmailVerification emailVerification, - IFileInfoManager fileInfoManager) + FileInfoManager fileInfoManager) { _store = store; _userUploads = userUploads; diff --git a/VoidCat/Services/Abstractions/IFileInfoManager.cs b/VoidCat/Services/Abstractions/IFileInfoManager.cs deleted file mode 100644 index 3320faf..0000000 --- a/VoidCat/Services/Abstractions/IFileInfoManager.cs +++ /dev/null @@ -1,38 +0,0 @@ -using VoidCat.Model; - -namespace VoidCat.Services.Abstractions; - -/// -/// Main interface for getting file info to serve to clients. -/// This interface should wrap all stores and return the combined result -/// -public interface IFileInfoManager -{ - /// - /// Get all metadata for a single file - /// - /// - /// - ValueTask Get(Guid id); - - /// - /// Get all private metadata for a single file - /// - /// - /// - ValueTask GetPrivate(Guid id); - - /// - /// Get all metadata for multiple files - /// - /// - /// - ValueTask> Get(Guid[] ids); - - /// - /// Deletes all file metadata - /// - /// - /// - ValueTask Delete(Guid id); -} diff --git a/VoidCat/Services/Background/DeleteExpiredFiles.cs b/VoidCat/Services/Background/DeleteExpiredFiles.cs new file mode 100644 index 0000000..2a5ec4e --- /dev/null +++ b/VoidCat/Services/Background/DeleteExpiredFiles.cs @@ -0,0 +1,52 @@ +using VoidCat.Model; +using VoidCat.Services.Abstractions; +using VoidCat.Services.Files; + +namespace VoidCat.Services.Background; + +/// +/// Delete expired files +/// +public sealed class DeleteExpiredFiles : BackgroundService +{ + private readonly ILogger _logger; + private readonly IServiceScopeFactory _scopeFactory; + + public DeleteExpiredFiles(ILogger logger, IServiceScopeFactory scopeFactory) + { + _logger = logger; + _scopeFactory = scopeFactory; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + using var scope = _scopeFactory.CreateScope(); + var metadata = scope.ServiceProvider.GetRequiredService(); + var fileInfoManager = scope.ServiceProvider.GetRequiredService(); + var fileStoreFactory = scope.ServiceProvider.GetRequiredService(); + + var files = await metadata.ListFiles(new(0, int.MaxValue)); + await foreach (var f in files.Results.WithCancellation(stoppingToken)) + { + try + { + if (f.Expires < DateTime.Now) + { + await fileStoreFactory.DeleteFile(f.Id); + await fileInfoManager.Delete(f.Id); + + _logger.LogInformation("Deleted file: {Id}", f.Id); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to delete file: {Id}", f.Id); + } + } + + await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); + } + } +} \ No newline at end of file diff --git a/VoidCat/Services/Background/DeleteUnverifiedAccounts.cs b/VoidCat/Services/Background/DeleteUnverifiedAccounts.cs index 59d9c8a..ef59ec0 100644 --- a/VoidCat/Services/Background/DeleteUnverifiedAccounts.cs +++ b/VoidCat/Services/Background/DeleteUnverifiedAccounts.cs @@ -25,7 +25,7 @@ public class DeleteUnverifiedAccounts : BackgroundService var userStore = scope.ServiceProvider.GetRequiredService(); var userUploads = scope.ServiceProvider.GetRequiredService(); var fileStore = scope.ServiceProvider.GetRequiredService(); - var fileInfoManager = scope.ServiceProvider.GetRequiredService(); + var fileInfoManager = scope.ServiceProvider.GetRequiredService(); var accounts = await userStore.ListUsers(new(0, Int32.MaxValue)); diff --git a/VoidCat/Services/Files/FileInfoManager.cs b/VoidCat/Services/Files/FileInfoManager.cs index a25d159..d8b320d 100644 --- a/VoidCat/Services/Files/FileInfoManager.cs +++ b/VoidCat/Services/Files/FileInfoManager.cs @@ -3,8 +3,11 @@ using VoidCat.Services.Abstractions; namespace VoidCat.Services.Files; -/// -public class FileInfoManager : IFileInfoManager +/// +/// Main interface for getting file info to serve to clients. +/// This interface should wrap all stores and return the combined result +/// +public sealed class FileInfoManager { private readonly IFileMetadataStore _metadataStore; private readonly IPaywallStore _paywallStore; @@ -24,19 +27,31 @@ public class FileInfoManager : IFileInfoManager _userUploadsStore = userUploadsStore; } - /// + /// + /// Get all metadata for a single file + /// + /// + /// public ValueTask Get(Guid id) { return Get(id); } - /// + /// + /// Get all private metadata for a single file + /// + /// + /// public ValueTask GetPrivate(Guid id) { return Get(id); } - /// + /// + /// Get all metadata for multiple files + /// + /// + /// public async ValueTask> Get(Guid[] ids) { var ret = new List(); @@ -52,7 +67,11 @@ public class FileInfoManager : IFileInfoManager return ret; } - /// + /// + /// Deletes all file metadata + /// + /// + /// public async ValueTask Delete(Guid id) { await _metadataStore.Delete(id); diff --git a/VoidCat/Services/Files/FileStorageStartup.cs b/VoidCat/Services/Files/FileStorageStartup.cs index b024c3a..ba89617 100644 --- a/VoidCat/Services/Files/FileStorageStartup.cs +++ b/VoidCat/Services/Files/FileStorageStartup.cs @@ -8,7 +8,7 @@ public static class FileStorageStartup { public static void AddStorage(this IServiceCollection services, VoidSettings settings) { - services.AddTransient(); + services.AddTransient(); services.AddTransient(); if (settings.CloudStorage != default) @@ -19,7 +19,7 @@ public static class FileStorageStartup services.AddTransient((svc) => new S3FileStore(s3, svc.GetRequiredService(), - svc.GetRequiredService(), + svc.GetRequiredService(), svc.GetRequiredService())); if (settings.MetadataStore == s3.Name) diff --git a/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs b/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs index ba1cab7..3d236d3 100644 --- a/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs +++ b/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs @@ -52,8 +52,8 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore oldMeta.Description = meta.Description ?? oldMeta.Description; oldMeta.Name = meta.Name ?? oldMeta.Name; oldMeta.MimeType = meta.MimeType ?? oldMeta.MimeType; - oldMeta.Expires = meta.Expires ?? oldMeta.Expires; oldMeta.Storage = meta.Storage ?? oldMeta.Storage; + oldMeta.Expires = meta.Expires; await Set(id, oldMeta); } diff --git a/VoidCat/Services/Files/LocalDiskFileStorage.cs b/VoidCat/Services/Files/LocalDiskFileStorage.cs index 8878211..889b671 100644 --- a/VoidCat/Services/Files/LocalDiskFileStorage.cs +++ b/VoidCat/Services/Files/LocalDiskFileStorage.cs @@ -8,14 +8,12 @@ namespace VoidCat.Services.Files; public class LocalDiskFileStore : StreamFileStore, IFileStore { private const string FilesDir = "files-v1"; - private readonly ILogger _logger; private readonly VoidSettings _settings; - public LocalDiskFileStore(ILogger logger, VoidSettings settings, IAggregateStatsCollector stats) + public LocalDiskFileStore(VoidSettings settings, IAggregateStatsCollector stats) : base(stats) { _settings = settings; - _logger = logger; var dir = Path.Combine(_settings.DataDirectory, FilesDir); if (!Directory.Exists(dir)) @@ -55,7 +53,6 @@ public class LocalDiskFileStore : StreamFileStore, IFileStore var fp = MapPath(id); if (File.Exists(fp)) { - _logger.LogInformation("Deleting file: {Path}", fp); File.Delete(fp); } diff --git a/VoidCat/Services/Files/PostgresFileMetadataStore.cs b/VoidCat/Services/Files/PostgresFileMetadataStore.cs index adfa12d..65cb82e 100644 --- a/VoidCat/Services/Files/PostgresFileMetadataStore.cs +++ b/VoidCat/Services/Files/PostgresFileMetadataStore.cs @@ -54,7 +54,7 @@ on conflict (""Id"") do update set mimeType = obj.MimeType, digest = obj.Digest, editSecret = obj.EditSecret, - expires = obj.Expires, + expires = obj.Expires?.ToUniversalTime(), store = obj.Storage }); } @@ -91,8 +91,8 @@ on conflict (""Id"") do update set oldMeta.Description = meta.Description ?? oldMeta.Description; oldMeta.Name = meta.Name ?? oldMeta.Name; oldMeta.MimeType = meta.MimeType ?? oldMeta.MimeType; - oldMeta.Expires = meta.Expires ?? oldMeta.Expires; oldMeta.Storage = meta.Storage ?? oldMeta.Storage; + oldMeta.Expires = meta.Expires; await Set(id, oldMeta); } diff --git a/VoidCat/Services/Files/S3FileMetadataStore.cs b/VoidCat/Services/Files/S3FileMetadataStore.cs index 4be3658..48a804d 100644 --- a/VoidCat/Services/Files/S3FileMetadataStore.cs +++ b/VoidCat/Services/Files/S3FileMetadataStore.cs @@ -53,8 +53,8 @@ public class S3FileMetadataStore : IFileMetadataStore oldMeta.Description = meta.Description ?? oldMeta.Description; oldMeta.Name = meta.Name ?? oldMeta.Name; oldMeta.MimeType = meta.MimeType ?? oldMeta.MimeType; - oldMeta.Expires = meta.Expires ?? oldMeta.Expires; oldMeta.Storage = meta.Storage ?? oldMeta.Storage; + oldMeta.Expires = meta.Expires; await Set(id, oldMeta); } diff --git a/VoidCat/Services/Files/S3FileStore.cs b/VoidCat/Services/Files/S3FileStore.cs index 5cb1e7c..4af3764 100644 --- a/VoidCat/Services/Files/S3FileStore.cs +++ b/VoidCat/Services/Files/S3FileStore.cs @@ -9,13 +9,13 @@ namespace VoidCat.Services.Files; /// public class S3FileStore : StreamFileStore, IFileStore { - private readonly IFileInfoManager _fileInfo; + private readonly FileInfoManager _fileInfo; private readonly AmazonS3Client _client; private readonly S3BlobConfig _config; private readonly IAggregateStatsCollector _statsCollector; private readonly ICache _cache; - public S3FileStore(S3BlobConfig settings, IAggregateStatsCollector stats, IFileInfoManager fileInfo, ICache cache) : base(stats) + public S3FileStore(S3BlobConfig settings, IAggregateStatsCollector stats, FileInfoManager fileInfo, ICache cache) : base(stats) { _fileInfo = fileInfo; _cache = cache; diff --git a/VoidCat/VoidStartup.cs b/VoidCat/VoidStartup.cs index 7ee911d..e5c80d3 100644 --- a/VoidCat/VoidStartup.cs +++ b/VoidCat/VoidStartup.cs @@ -155,6 +155,7 @@ public static class VoidStartup public static void AddBackgroundServices(this IServiceCollection services, VoidSettings voidSettings) { services.AddHostedService(); + services.AddHostedService(); if (voidSettings.HasVirusScanner()) { diff --git a/VoidCat/spa/src/ApiKeyList.js b/VoidCat/spa/src/ApiKeyList.js index 722d3ed..7eaff29 100644 --- a/VoidCat/spa/src/ApiKeyList.js +++ b/VoidCat/spa/src/ApiKeyList.js @@ -23,7 +23,11 @@ export default function ApiKeyList() { setNewApiKey(await rsp.json()); } } - + + function openDocs() { + window.open("/swagger", "_blank") + } + useEffect(() => { if (Api) { loadApiKeys(); @@ -38,6 +42,7 @@ export default function ApiKeyList() {
createApiKey()}>+New + openDocs()}>Docs
diff --git a/VoidCat/spa/src/FileEdit.js b/VoidCat/spa/src/FileEdit.js index 810f747..5ddc622 100644 --- a/VoidCat/spa/src/FileEdit.js +++ b/VoidCat/spa/src/FileEdit.js @@ -6,6 +6,7 @@ import {useApi} from "./Api"; import "./FileEdit.css"; import {useSelector} from "react-redux"; import {VoidButton} from "./VoidButton"; +import moment from "moment"; export function FileEdit(props) { const {Api} = useApi(); @@ -15,9 +16,10 @@ export function FileEdit(props) { const [paywall, setPaywall] = useState(file.paywall?.service); const [name, setName] = useState(meta?.name); const [description, setDescription] = useState(meta?.description); + const [expiry, setExpiry] = useState(meta?.expires === undefined || meta?.expires === null ? null : moment(meta?.expires).unix() * 1000); - const privateFile = file?.uploader?.id && profile?.id === file.uploader.id - ? file + const privateFile = file?.uploader?.id && profile?.id === file.uploader.id + ? file : JSON.parse(window.localStorage.getItem(file.id)); if (!privateFile || privateFile?.metadata?.editSecret === null) { return null; @@ -32,11 +34,12 @@ export function FileEdit(props) { let meta = { name, description, - editSecret: privateFile?.metadata?.editSecret + editSecret: privateFile?.metadata?.editSecret, + expires: moment(expiry).toISOString() }; - await Api.updateMetadata(file.id, meta); + await Api.updateMetadata(file.id, meta); } - + function renderPaywallConfig() { switch (paywall) { case 0: { @@ -58,6 +61,22 @@ export function FileEdit(props) {
setName(e.target.value)}/>
Description:
setDescription(e.target.value)}/>
+
Expiry
+
+ { + console.log(e.target.value); + if (e.target.value.length > 0) { + setExpiry(moment.utc(e.target.value).unix() * 1000); + } else { + setExpiry(null); + } + + }}/> +
saveMeta()} options={{showSuccess: true}}>Save diff --git a/VoidCat/spa/src/index.css b/VoidCat/spa/src/index.css index 462da48..99ae936 100644 --- a/VoidCat/spa/src/index.css +++ b/VoidCat/spa/src/index.css @@ -60,7 +60,7 @@ a:hover { align-items: center; } -input[type="text"], input[type="number"], input[type="password"], select { +input[type="text"], input[type="number"], input[type="password"], input[type="datetime-local"], select { display: inline-block; line-height: 1.1; border-radius: 10px;