♻️ File organize system v2
This commit is contained in:
@@ -23,6 +23,9 @@ public class AppDatabase(
|
|||||||
public DbSet<QuotaRecord> QuotaRecords { get; set; } = null!;
|
public DbSet<QuotaRecord> QuotaRecords { get; set; } = null!;
|
||||||
|
|
||||||
public DbSet<SnCloudFile> Files { get; set; } = null!;
|
public DbSet<SnCloudFile> Files { get; set; } = null!;
|
||||||
|
public DbSet<SnFileObject> FileObjects { get; set; } = null!;
|
||||||
|
public DbSet<SnFileReplica> FileReplicas { get; set; } = null!;
|
||||||
|
public DbSet<SnFilePermission> FilePermissions { get; set; } = null!;
|
||||||
public DbSet<SnCloudFileReference> FileReferences { get; set; } = null!;
|
public DbSet<SnCloudFileReference> FileReferences { get; set; } = null!;
|
||||||
public DbSet<SnCloudFileIndex> FileIndexes { get; set; }
|
public DbSet<SnCloudFileIndex> FileIndexes { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using DysonNetwork.Drive;
|
using DysonNetwork.Drive;
|
||||||
using DysonNetwork.Drive.Startup;
|
using DysonNetwork.Drive.Startup;
|
||||||
|
using DysonNetwork.Drive.Storage;
|
||||||
using DysonNetwork.Shared.Auth;
|
using DysonNetwork.Shared.Auth;
|
||||||
using DysonNetwork.Shared.Http;
|
using DysonNetwork.Shared.Http;
|
||||||
using DysonNetwork.Shared.Registry;
|
using DysonNetwork.Shared.Registry;
|
||||||
@@ -38,6 +39,10 @@ using (var scope = app.Services.CreateScope())
|
|||||||
{
|
{
|
||||||
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||||
await db.Database.MigrateAsync();
|
await db.Database.MigrateAsync();
|
||||||
|
|
||||||
|
// Run one-time migration
|
||||||
|
var migrationService = scope.ServiceProvider.GetRequiredService<FileMigrationService>();
|
||||||
|
await migrationService.MigrateCloudFilesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.ConfigureAppMiddleware();
|
app.ConfigureAppMiddleware();
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ public static class ServiceCollectionExtensions
|
|||||||
|
|
||||||
public IServiceCollection AddAppBusinessServices()
|
public IServiceCollection AddAppBusinessServices()
|
||||||
{
|
{
|
||||||
|
services.AddScoped<Storage.FileMigrationService>();
|
||||||
services.AddScoped<Storage.FileService>();
|
services.AddScoped<Storage.FileService>();
|
||||||
services.AddScoped<Storage.FileReferenceService>();
|
services.AddScoped<Storage.FileReferenceService>();
|
||||||
services.AddScoped<Storage.PersistentTaskService>();
|
services.AddScoped<Storage.PersistentTaskService>();
|
||||||
|
|||||||
59
DysonNetwork.Drive/Storage/FileMigrationService.cs
Normal file
59
DysonNetwork.Drive/Storage/FileMigrationService.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Drive.Storage;
|
||||||
|
|
||||||
|
public class FileMigrationService(AppDatabase db, ILogger<FileMigrationService> logger)
|
||||||
|
{
|
||||||
|
public async Task MigrateCloudFilesAsync()
|
||||||
|
{
|
||||||
|
logger.LogInformation("Starting cloud file migration.");
|
||||||
|
|
||||||
|
var cloudFiles = await db.Files
|
||||||
|
.Where(f =>
|
||||||
|
f.ObjectId == null &&
|
||||||
|
f.StorageId != null &&
|
||||||
|
f.PoolId != null &&
|
||||||
|
!db.FileObjects.Any(fo => fo.Id == f.Id)
|
||||||
|
)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
logger.LogDebug("Found {Count} cloud files to migrate.", cloudFiles.Count);
|
||||||
|
|
||||||
|
foreach (var cf in cloudFiles)
|
||||||
|
{
|
||||||
|
var fileObject = new SnFileObject
|
||||||
|
{
|
||||||
|
Id = cf.Id,
|
||||||
|
Size = cf.Size,
|
||||||
|
Meta = cf.FileMeta,
|
||||||
|
MimeType = cf.MimeType,
|
||||||
|
Hash = cf.Hash,
|
||||||
|
HasCompression = cf.HasCompression,
|
||||||
|
HasThumbnail = cf.HasThumbnail
|
||||||
|
};
|
||||||
|
|
||||||
|
var fileReplica = new SnFileReplica
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
ObjectId = fileObject.Id,
|
||||||
|
PoolId = cf.PoolId!.Value,
|
||||||
|
StorageId = cf.StorageId ?? cf.Id,
|
||||||
|
Status = SnFileReplicaStatus.Available,
|
||||||
|
IsPrimary = true
|
||||||
|
};
|
||||||
|
|
||||||
|
fileObject.FileReplicas.Add(fileReplica);
|
||||||
|
|
||||||
|
db.FileObjects.Add(fileObject);
|
||||||
|
db.FileReplicas.Add(fileReplica);
|
||||||
|
|
||||||
|
cf.ObjectId = fileObject.Id;
|
||||||
|
cf.Object = fileObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
logger.LogInformation("Cloud file migration completed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -762,27 +762,6 @@ public class FileService(
|
|||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> CreateFastUploadLinkAsync(SnCloudFile file)
|
|
||||||
{
|
|
||||||
if (file.PoolId is null) throw new InvalidOperationException("Pool ID is null");
|
|
||||||
|
|
||||||
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);
|
|
||||||
if (client is null)
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Failed to configure client for remote destination '{file.PoolId}'"
|
|
||||||
);
|
|
||||||
|
|
||||||
var url = await client.PresignedPutObjectAsync(
|
|
||||||
new PresignedPutObjectArgs()
|
|
||||||
.WithBucket(dest.Bucket)
|
|
||||||
.WithObject(file.Id)
|
|
||||||
.WithExpiry(60 * 60 * 24)
|
|
||||||
);
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file class UpdatableCloudFile(SnCloudFile file)
|
file class UpdatableCloudFile(SnCloudFile file)
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ public class SnCloudFile : ModelBase, ICloudFile, IIdentifiedResource
|
|||||||
public bool HasThumbnail { get; set; } = false;
|
public bool HasThumbnail { get; set; } = false;
|
||||||
public bool IsEncrypted { get; set; } = false;
|
public bool IsEncrypted { get; set; } = false;
|
||||||
|
|
||||||
|
[MaxLength(32)] public string? ObjectId { get; set; }
|
||||||
|
public SnFileObject? Object { get; set; }
|
||||||
|
|
||||||
public FilePool? Pool { get; set; }
|
public FilePool? Pool { get; set; }
|
||||||
public Guid? PoolId { get; set; }
|
public Guid? PoolId { get; set; }
|
||||||
[JsonIgnore] public SnFileBundle? Bundle { get; set; }
|
[JsonIgnore] public SnFileBundle? Bundle { get; set; }
|
||||||
|
|||||||
67
DysonNetwork.Shared/Models/CloudFileObject.cs
Normal file
67
DysonNetwork.Shared/Models/CloudFileObject.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Models;
|
||||||
|
|
||||||
|
public class SnFileObject : ModelBase
|
||||||
|
{
|
||||||
|
[MaxLength(32)] public string Id { get; set; }
|
||||||
|
|
||||||
|
public long Size { get; set; }
|
||||||
|
|
||||||
|
[Column(TypeName = "jsonb")] public Dictionary<string, object?>? Meta { get; set; }
|
||||||
|
[MaxLength(256)] public string? MimeType { get; set; }
|
||||||
|
[MaxLength(256)] public string? Hash { get; set; }
|
||||||
|
public bool HasCompression { get; set; } = false;
|
||||||
|
public bool HasThumbnail { get; set; } = false;
|
||||||
|
|
||||||
|
public List<SnFileReplica> FileReplicas { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SnFileReplicaStatus
|
||||||
|
{
|
||||||
|
Pending,
|
||||||
|
Uploading,
|
||||||
|
Available,
|
||||||
|
Failed,
|
||||||
|
Deprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SnFileReplica : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(32)] public string ObjectId { get; set; }
|
||||||
|
public SnFileObject Object { get; set; } = null!;
|
||||||
|
|
||||||
|
public Guid PoolId { get; set; }
|
||||||
|
public FilePool Pool { get; set; } = null!;
|
||||||
|
|
||||||
|
[MaxLength(128)]
|
||||||
|
public string StorageId { get; set; } = null!;
|
||||||
|
|
||||||
|
public SnFileReplicaStatus Status { get; set; } = SnFileReplicaStatus.Pending;
|
||||||
|
|
||||||
|
public bool IsPrimary { get; set; } = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SnFilePermissionType
|
||||||
|
{
|
||||||
|
Someone,
|
||||||
|
Anyone
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SnFilePermissionLevel
|
||||||
|
{
|
||||||
|
Read,
|
||||||
|
Write
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SnFilePermission : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string FileId { get; set; } = null!;
|
||||||
|
public SnFilePermissionType SubjectType { get; set; } = SnFilePermissionType.Someone;
|
||||||
|
public string SubjectId { get; set; } = null!;
|
||||||
|
public SnFilePermissionLevel Permission { get; set; } = SnFilePermissionLevel.Read;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user