diff --git a/DysonNetwork.Drive/Billing/UsageService.cs b/DysonNetwork.Drive/Billing/UsageService.cs index 9e2a5a3..b4e2e57 100644 --- a/DysonNetwork.Drive/Billing/UsageService.cs +++ b/DysonNetwork.Drive/Billing/UsageService.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using NodaTime; namespace DysonNetwork.Drive.Billing; @@ -28,15 +29,21 @@ public class UsageService(AppDatabase db) { public async Task GetTotalUsage() { + var now = SystemClock.Instance.GetCurrentInstant(); + var fileQuery = db.Files + .Where(f => !f.IsMarkedRecycle) + .Where(f => !f.ExpiredAt.HasValue || f.ExpiredAt > now) + .AsQueryable(); + var poolUsages = await db.Pools .Select(p => new UsageDetails { PoolId = p.Id, PoolName = p.Name, - UsageBytes = db.Files + UsageBytes = fileQuery .Where(f => f.PoolId == p.Id) .Sum(f => f.Size), - Cost = db.Files + Cost = fileQuery .Where(f => f.PoolId == p.Id) .Sum(f => f.Size) / 1024.0 / 1024.0 * (p.BillingConfig.CostMultiplier ?? 1.0), @@ -75,13 +82,17 @@ public class UsageService(AppDatabase db) { return null; } + + var now = SystemClock.Instance.GetCurrentInstant(); + var fileQuery = db.Files + .Where(f => !f.IsMarkedRecycle) + .Where(f => f.ExpiredAt.HasValue && f.ExpiredAt > now) + .AsQueryable(); - var usageBytes = await db.Files - .Where(f => f.PoolId == poolId) + var usageBytes = await fileQuery .SumAsync(f => f.Size); - var fileCount = await db.Files - .Where(f => f.PoolId == poolId) + var fileCount = await fileQuery .CountAsync(); var cost = usageBytes / 1024.0 / 1024.0 * diff --git a/DysonNetwork.Drive/Storage/FileController.cs b/DysonNetwork.Drive/Storage/FileController.cs index 0a79310..6113841 100644 --- a/DysonNetwork.Drive/Storage/FileController.cs +++ b/DysonNetwork.Drive/Storage/FileController.cs @@ -35,6 +35,7 @@ public class FileController( var file = await fs.GetFileAsync(id); if (file is null) return NotFound(); + if (file.IsMarkedRecycle) return StatusCode(StatusCodes.Status410Gone, "The file has been recycled."); if (!string.IsNullOrWhiteSpace(file.StorageUrl)) return Redirect(file.StorageUrl); @@ -149,7 +150,7 @@ public class FileController( .AsQueryable(); if (pool.HasValue) query = query.Where(e => e.PoolId == pool); - + var total = await query.CountAsync(); Response.Headers.Append("X-Total", total.ToString()); diff --git a/DysonNetwork.Drive/Storage/FileService.cs b/DysonNetwork.Drive/Storage/FileService.cs index a6eea7b..bf99ae0 100644 --- a/DysonNetwork.Drive/Storage/FileService.cs +++ b/DysonNetwork.Drive/Storage/FileService.cs @@ -153,25 +153,26 @@ public class FileService( IsEncrypted = !string.IsNullOrWhiteSpace(encryptPassword) && pool.PolicyConfig.AllowEncryption }; - var existingFile = await db.Files.AsNoTracking().FirstOrDefaultAsync(f => f.Hash == hash); - file.StorageId = existingFile?.StorageId ?? file.Id; - - if (existingFile is not null) - { - file.FileMeta = existingFile.FileMeta; - file.HasCompression = existingFile.HasCompression; - file.SensitiveMarks = existingFile.SensitiveMarks; - file.MimeType = existingFile.MimeType; - file.UploadedAt = existingFile.UploadedAt; - file.PoolId = existingFile.PoolId; - - db.Files.Add(file); - await db.SaveChangesAsync(); - // Since the file content is a duplicate, we can delete the new upload and we are done. - await stream.DisposeAsync(); - await store.DeleteFileAsync(file.Id, CancellationToken.None); - return file; - } + // TODO: Enable the feature later + // var existingFile = await db.Files.AsNoTracking().FirstOrDefaultAsync(f => f.Hash == hash); + // file.StorageId = existingFile?.StorageId ?? file.Id; + // + // if (existingFile is not null) + // { + // file.FileMeta = existingFile.FileMeta; + // file.HasCompression = existingFile.HasCompression; + // file.SensitiveMarks = existingFile.SensitiveMarks; + // file.MimeType = existingFile.MimeType; + // file.UploadedAt = existingFile.UploadedAt; + // file.PoolId = existingFile.PoolId; + // + // db.Files.Add(file); + // await db.SaveChangesAsync(); + // // Since the file content is a duplicate, we can delete the new upload and we are done. + // await stream.DisposeAsync(); + // await store.DeleteFileAsync(file.Id, CancellationToken.None); + // return file; + // } // Extract metadata on the current thread for a faster initial response if (!pool.PolicyConfig.NoMetadata) @@ -540,11 +541,11 @@ public class FileService( public async Task DeleteFileAsync(CloudFile file) { - await DeleteFileDataAsync(file); - db.Remove(file); await db.SaveChangesAsync(); await _PurgeCacheAsync(file.Id); + + await DeleteFileDataAsync(file); } public async Task DeleteFileDataAsync(CloudFile file) @@ -559,17 +560,10 @@ public class FileService( .ToListAsync(); // Check if any of these files are referenced - var anyReferenced = false; if (sameOriginFiles.Count != 0) - { - anyReferenced = await db.FileReferences - .Where(r => sameOriginFiles.Contains(r.FileId)) - .AnyAsync(); - } + return; // If any other file with the same storage ID is referenced, don't delete the actual file data - if (anyReferenced) return; - var dest = await GetRemoteStorageConfig(file.PoolId.Value); if (dest is null) throw new InvalidOperationException($"No remote storage configured for pool {file.PoolId}"); var client = CreateMinioClient(dest);