♻️ Extract the Storage service to DysonNetwork.Drive microservice
This commit is contained in:
321
DysonNetwork.Drive/Services/FileReferenceService.cs
Normal file
321
DysonNetwork.Drive/Services/FileReferenceService.cs
Normal file
@ -0,0 +1,321 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DysonNetwork.Drive.Data;
|
||||
using DysonNetwork.Drive.Interfaces;
|
||||
using DysonNetwork.Drive.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Drive.Services;
|
||||
|
||||
public class FileReferenceService : IFileReferenceService, IDisposable
|
||||
{
|
||||
private readonly AppDatabase _dbContext;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly IClock _clock;
|
||||
private readonly ILogger<FileReferenceService> _logger;
|
||||
private bool _disposed = false;
|
||||
|
||||
public FileReferenceService(
|
||||
AppDatabase dbContext,
|
||||
IFileService fileService,
|
||||
IClock clock,
|
||||
ILogger<FileReferenceService> logger)
|
||||
{
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
_fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
|
||||
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<CloudFileReference> CreateReferenceAsync(
|
||||
Guid fileId,
|
||||
string resourceId,
|
||||
string resourceType,
|
||||
string referenceType,
|
||||
string? referenceId = null,
|
||||
string? referenceName = null,
|
||||
string? referenceMimeType = null,
|
||||
long? referenceSize = null,
|
||||
string? referenceUrl = null,
|
||||
string? referenceThumbnailUrl = null,
|
||||
string? referencePreviewUrl = null,
|
||||
string? referenceMetadata = null,
|
||||
IDictionary<string, object>? metadata = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Verify file exists
|
||||
var fileExists = await _fileService.FileExistsAsync(fileId, cancellationToken);
|
||||
if (!fileExists)
|
||||
{
|
||||
throw new FileNotFoundException($"File with ID {fileId} not found.");
|
||||
}
|
||||
|
||||
var reference = new CloudFileReference
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
FileId = fileId,
|
||||
ResourceId = resourceId,
|
||||
ResourceType = resourceType,
|
||||
ReferenceType = referenceType,
|
||||
ReferenceId = referenceId,
|
||||
ReferenceName = referenceName,
|
||||
ReferenceMimeType = referenceMimeType,
|
||||
ReferenceSize = referenceSize,
|
||||
ReferenceUrl = referenceUrl,
|
||||
ReferenceThumbnailUrl = referenceThumbnailUrl,
|
||||
ReferencePreviewUrl = referencePreviewUrl,
|
||||
ReferenceMetadata = referenceMetadata,
|
||||
IsActive = true,
|
||||
CreatedAt = _clock.GetCurrentInstant().ToDateTimeOffset()
|
||||
};
|
||||
|
||||
if (metadata != null && metadata.Any())
|
||||
{
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
reference.Metadata = JsonDocument.Parse(JsonSerializer.Serialize(metadata, options));
|
||||
}
|
||||
|
||||
_dbContext.FileReferences.Add(reference);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created reference {ReferenceId} for file {FileId} to resource {ResourceType}/{ResourceId}",
|
||||
reference.Id, fileId, resourceType, resourceId);
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
public async Task<CloudFileReference> GetReferenceAsync(Guid referenceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var reference = await _dbContext.FileReferences
|
||||
.AsNoTracking()
|
||||
.Include(r => r.File)
|
||||
.FirstOrDefaultAsync(r => r.Id == referenceId, cancellationToken);
|
||||
|
||||
if (reference == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Reference with ID {referenceId} not found.");
|
||||
}
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CloudFileReference>> GetReferencesForFileAsync(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _dbContext.FileReferences
|
||||
.AsNoTracking()
|
||||
.Where(r => r.FileId == fileId && r.IsActive)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CloudFileReference>> GetReferencesForResourceAsync(
|
||||
string resourceId,
|
||||
string resourceType,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _dbContext.FileReferences
|
||||
.AsNoTracking()
|
||||
.Where(r => r.ResourceId == resourceId &&
|
||||
r.ResourceType == resourceType &&
|
||||
r.IsActive)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CloudFileReference>> GetReferencesOfTypeAsync(
|
||||
string referenceType,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _dbContext.FileReferences
|
||||
.AsNoTracking()
|
||||
.Where(r => r.ReferenceType == referenceType && r.IsActive)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteReferenceAsync(Guid referenceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var reference = await _dbContext.FileReferences
|
||||
.FirstOrDefaultAsync(r => r.Id == referenceId, cancellationToken);
|
||||
|
||||
if (reference == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
reference.IsActive = false;
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Deleted reference {ReferenceId}", referenceId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<int> DeleteReferencesForFileAsync(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var references = await _dbContext.FileReferences
|
||||
.Where(r => r.FileId == fileId && r.IsActive)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var reference in references)
|
||||
{
|
||||
reference.IsActive = false;
|
||||
}
|
||||
|
||||
var count = await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Deleted {Count} references for file {FileId}", count, fileId);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public async Task<int> DeleteReferencesForResourceAsync(
|
||||
string resourceId,
|
||||
string resourceType,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var references = await _dbContext.FileReferences
|
||||
.Where(r => r.ResourceId == resourceId &&
|
||||
r.ResourceType == resourceType &&
|
||||
r.IsActive)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var reference in references)
|
||||
{
|
||||
reference.IsActive = false;
|
||||
}
|
||||
|
||||
var count = await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Deleted {Count} references for resource {ResourceType}/{ResourceId}",
|
||||
count, resourceType, resourceId);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public async Task<CloudFileReference> UpdateReferenceMetadataAsync(
|
||||
Guid referenceId,
|
||||
IDictionary<string, object> metadata,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var reference = await GetReferenceAsync(referenceId, cancellationToken);
|
||||
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
reference.Metadata = JsonDocument.Parse(JsonSerializer.Serialize(metadata, options));
|
||||
reference.UpdatedAt = _clock.GetCurrentInstant().ToDateTimeOffset();
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Updated metadata for reference {ReferenceId}", referenceId);
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
public async Task<bool> ReferenceExistsAsync(Guid referenceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _dbContext.FileReferences
|
||||
.AsNoTracking()
|
||||
.AnyAsync(r => r.Id == referenceId && r.IsActive, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> HasReferenceAsync(
|
||||
Guid fileId,
|
||||
string resourceId,
|
||||
string resourceType,
|
||||
string? referenceType = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _dbContext.FileReferences
|
||||
.AsNoTracking()
|
||||
.Where(r => r.FileId == fileId &&
|
||||
r.ResourceId == resourceId &&
|
||||
r.ResourceType == resourceType &&
|
||||
r.IsActive);
|
||||
|
||||
if (!string.IsNullOrEmpty(referenceType))
|
||||
{
|
||||
query = query.Where(r => r.ReferenceType == referenceType);
|
||||
}
|
||||
|
||||
return await query.AnyAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<CloudFileReference> UpdateReferenceResourceAsync(
|
||||
Guid referenceId,
|
||||
string newResourceId,
|
||||
string newResourceType,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var reference = await GetReferenceAsync(referenceId, cancellationToken);
|
||||
|
||||
reference.ResourceId = newResourceId;
|
||||
reference.ResourceType = newResourceType;
|
||||
reference.UpdatedAt = _clock.GetCurrentInstant().ToDateTimeOffset();
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Updated reference {ReferenceId} to point to resource {ResourceType}/{ResourceId}",
|
||||
referenceId, newResourceType, newResourceId);
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CloudFile>> GetFilesForResourceAsync(
|
||||
string resourceId,
|
||||
string resourceType,
|
||||
string? referenceType = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _dbContext.FileReferences
|
||||
.AsNoTracking()
|
||||
.Include(r => r.File)
|
||||
.Where(r => r.ResourceId == resourceId &&
|
||||
r.ResourceType == resourceType &&
|
||||
r.IsActive);
|
||||
|
||||
if (!string.IsNullOrEmpty(referenceType))
|
||||
{
|
||||
query = query.Where(r => r.ReferenceType == referenceType);
|
||||
}
|
||||
|
||||
var references = await query.ToListAsync(cancellationToken);
|
||||
return references.Select(r => r.File!);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CloudFile>> GetFilesForReferenceTypeAsync(
|
||||
string referenceType,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var references = await _dbContext.FileReferences
|
||||
.AsNoTracking()
|
||||
.Include(r => r.File)
|
||||
.Where(r => r.ReferenceType == referenceType && r.IsActive)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return references.Select(r => r.File!);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_dbContext?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
301
DysonNetwork.Drive/Services/FileService.cs
Normal file
301
DysonNetwork.Drive/Services/FileService.cs
Normal file
@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DysonNetwork.Drive.Data;
|
||||
using DysonNetwork.Drive.Extensions;
|
||||
using DysonNetwork.Drive.Interfaces;
|
||||
using DysonNetwork.Drive.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Drive.Services;
|
||||
|
||||
public class FileService : IFileService, IDisposable
|
||||
{
|
||||
private readonly ILogger<FileService> _logger;
|
||||
private readonly AppDatabase _dbContext;
|
||||
private readonly IClock _clock;
|
||||
private bool _disposed = false;
|
||||
|
||||
public FileService(AppDatabase dbContext, IClock clock, ILogger<FileService> logger)
|
||||
{
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<CloudFile> GetFileAsync(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await _dbContext.Files
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(f => f.Id == fileId, cancellationToken);
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
throw new FileNotFoundException($"File with ID {fileId} not found.");
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
public async Task<Stream> DownloadFileAsync(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await GetFileAsync(fileId, cancellationToken);
|
||||
|
||||
// In a real implementation, this would stream the file from storage (e.g., S3, local filesystem)
|
||||
// For now, we'll return a MemoryStream with a placeholder
|
||||
var placeholder = $"This is a placeholder for file {fileId} with name {file.Name}";
|
||||
var memoryStream = new MemoryStream();
|
||||
var writer = new StreamWriter(memoryStream);
|
||||
await writer.WriteAsync(placeholder);
|
||||
await writer.FlushAsync();
|
||||
memoryStream.Position = 0;
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
public async Task<CloudFile> UploadFileAsync(
|
||||
Stream fileStream,
|
||||
string fileName,
|
||||
string contentType,
|
||||
IDictionary<string, string>? metadata = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (fileStream == null) throw new ArgumentNullException(nameof(fileStream));
|
||||
if (string.IsNullOrWhiteSpace(fileName)) throw new ArgumentNullException(nameof(fileName));
|
||||
if (string.IsNullOrWhiteSpace(contentType)) throw new ArgumentNullException(nameof(contentType));
|
||||
|
||||
// In a real implementation, this would upload to a storage service
|
||||
var now = _clock.GetCurrentInstant();
|
||||
var file = new CloudFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = Path.GetFileName(fileName),
|
||||
OriginalName = fileName,
|
||||
MimeType = contentType,
|
||||
Size = fileStream.Length,
|
||||
StoragePath = $"uploads/{now.ToUnixTimeMilliseconds()}/{Guid.NewGuid()}/{Path.GetFileName(fileName)}",
|
||||
StorageProvider = "local", // or "s3", "azure", etc.
|
||||
CreatedAt = now.ToDateTimeOffset(),
|
||||
IsPublic = false,
|
||||
IsTemporary = false,
|
||||
IsDeleted = false
|
||||
};
|
||||
|
||||
if (metadata != null)
|
||||
{
|
||||
file.Metadata = System.Text.Json.JsonSerializer.Serialize(metadata);
|
||||
}
|
||||
|
||||
_dbContext.Files.Add(file);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Uploaded file {FileId} with name {FileName}", file.Id, file.Name);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteFileAsync(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await _dbContext.Files.FindAsync(new object[] { fileId }, cancellationToken);
|
||||
if (file == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// In a real implementation, this would also delete the file from storage
|
||||
file.IsDeleted = true;
|
||||
file.DeletedAt = _clock.GetCurrentInstant();
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Soft-deleted file {FileId}", fileId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<CloudFile> UpdateFileMetadataAsync(Guid fileId, IDictionary<string, string> metadata, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await GetFileAsync(fileId, cancellationToken);
|
||||
|
||||
file.Metadata = System.Text.Json.JsonSerializer.Serialize(metadata);
|
||||
var now = _clock.GetCurrentInstant();
|
||||
file.UpdatedAt = new DateTimeOffset(now.ToDateTimeUtc(), TimeSpan.Zero);
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Updated metadata for file {FileId}", fileId);
|
||||
return file;
|
||||
}
|
||||
|
||||
public Task<bool> FileExistsAsync(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _dbContext.Files
|
||||
.AsNoTracking()
|
||||
.AnyAsync(f => f.Id == fileId && !f.IsDeleted, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<string> GetFileUrlAsync(Guid fileId, TimeSpan? expiry = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// In a real implementation, this would generate a signed URL with the specified expiry
|
||||
return Task.FromResult($"https://storage.dyson.network/files/{fileId}");
|
||||
}
|
||||
|
||||
public Task<string> GetFileThumbnailUrlAsync(Guid fileId, int? width = null, int? height = null, TimeSpan? expiry = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// In a real implementation, this would generate a signed thumbnail URL
|
||||
var size = width.HasValue || height.HasValue
|
||||
? $"_{width ?? 0}x{height ?? 0}"
|
||||
: string.Empty;
|
||||
|
||||
return Task.FromResult($"https://storage.dyson.network/thumbnails/{fileId}{size}");
|
||||
}
|
||||
|
||||
public async Task<CloudFile> CopyFileAsync(Guid sourceFileId, string? newName = null, IDictionary<string, string>? newMetadata = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var sourceFile = await GetFileAsync(sourceFileId, cancellationToken);
|
||||
|
||||
var newFile = new CloudFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = newName ?? sourceFile.Name,
|
||||
OriginalName = sourceFile.OriginalName,
|
||||
MimeType = sourceFile.MimeType,
|
||||
Size = sourceFile.Size,
|
||||
StoragePath = $"copies/{_clock.GetCurrentInstant().ToUnixTimeMilliseconds()}/{Guid.NewGuid()}/{sourceFile.Name}",
|
||||
StorageProvider = sourceFile.StorageProvider,
|
||||
ContentHash = sourceFile.ContentHash,
|
||||
ThumbnailPath = sourceFile.ThumbnailPath,
|
||||
PreviewPath = sourceFile.PreviewPath,
|
||||
Width = sourceFile.Width,
|
||||
Height = sourceFile.Height,
|
||||
Duration = sourceFile.Duration,
|
||||
Metadata = newMetadata != null
|
||||
? System.Text.Json.JsonSerializer.Serialize(newMetadata)
|
||||
: sourceFile.Metadata,
|
||||
IsPublic = sourceFile.IsPublic,
|
||||
IsTemporary = sourceFile.IsTemporary,
|
||||
IsDeleted = false,
|
||||
ExpiresAt = sourceFile.ExpiresAt,
|
||||
UploadedById = sourceFile.UploadedById,
|
||||
UploadedByType = sourceFile.UploadedByType,
|
||||
CreatedAt = _clock.GetCurrentInstant().ToDateTimeOffset(),
|
||||
UpdatedAt = _clock.GetCurrentInstant().ToDateTimeOffset()
|
||||
};
|
||||
|
||||
_dbContext.Files.Add(newFile);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Copied file {SourceFileId} to {NewFileId}", sourceFileId, newFile.Id);
|
||||
|
||||
return newFile;
|
||||
}
|
||||
|
||||
public async Task<CloudFile> MoveFileAsync(Guid sourceFileId, string? newName = null, IDictionary<string, string>? newMetadata = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var sourceFile = await GetFileAsync(sourceFileId, cancellationToken);
|
||||
|
||||
// In a real implementation, this would move the file in storage
|
||||
var newPath = $"moved/{_clock.GetCurrentInstant().ToUnixTimeMilliseconds()}/{Guid.NewGuid()}/{newName ?? sourceFile.Name}";
|
||||
|
||||
sourceFile.Name = newName ?? sourceFile.Name;
|
||||
sourceFile.StoragePath = newPath;
|
||||
sourceFile.UpdatedAt = _clock.GetCurrentInstant();
|
||||
|
||||
if (newMetadata != null)
|
||||
{
|
||||
sourceFile.Metadata = System.Text.Json.JsonSerializer.Serialize(newMetadata);
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Moved file {FileId} to {NewPath}", sourceFileId, newPath);
|
||||
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
public async Task<CloudFile> RenameFileAsync(Guid fileId, string newName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await GetFileAsync(fileId, cancellationToken);
|
||||
|
||||
file.Name = newName;
|
||||
file.UpdatedAt = _clock.GetCurrentInstant().ToDateTimeOffset();
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Renamed file {FileId} to {NewName}", fileId, newName);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
public async Task<long> GetFileSizeAsync(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await GetFileAsync(fileId, cancellationToken);
|
||||
return file.Size;
|
||||
}
|
||||
|
||||
public async Task<string> GetFileHashAsync(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await GetFileAsync(fileId, cancellationToken);
|
||||
|
||||
if (string.IsNullOrEmpty(file.ContentHash))
|
||||
{
|
||||
// In a real implementation, this would compute the hash of the file content
|
||||
file.ContentHash = Convert.ToBase64String(Guid.NewGuid().ToByteArray())[..16];
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return file.ContentHash;
|
||||
}
|
||||
|
||||
public async Task<Stream> GetFileThumbnailAsync(Guid fileId, int? width = null, int? height = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// In a real implementation, this would generate or retrieve a thumbnail
|
||||
var placeholder = $"This is a thumbnail for file {fileId} with size {width ?? 0}x{height ?? 0}";
|
||||
var memoryStream = new MemoryStream();
|
||||
var writer = new StreamWriter(memoryStream);
|
||||
await writer.WriteAsync(placeholder);
|
||||
await writer.FlushAsync();
|
||||
memoryStream.Position = 0;
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
public async Task<CloudFile> SetFileVisibilityAsync(Guid fileId, bool isPublic, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await GetFileAsync(fileId, cancellationToken);
|
||||
|
||||
if (file.IsPublic != isPublic)
|
||||
{
|
||||
file.IsPublic = isPublic;
|
||||
file.UpdatedAt = _clock.GetCurrentInstant().ToDateTimeOffset();
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Set visibility of file {FileId} to {Visibility}", fileId, isPublic ? "public" : "private");
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_dbContext?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
13
DysonNetwork.Drive/Services/ICacheService.cs
Normal file
13
DysonNetwork.Drive/Services/ICacheService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DysonNetwork.Drive.Services;
|
||||
|
||||
public interface ICacheService
|
||||
{
|
||||
Task<T?> GetAsync<T>(string key);
|
||||
Task SetAsync<T>(string key, T value, System.TimeSpan? expiry = null);
|
||||
Task RemoveAsync(string key);
|
||||
Task<bool> ExistsAsync(string key);
|
||||
Task<long> IncrementAsync(string key, long value = 1);
|
||||
Task<long> DecrementAsync(string key, long value = 1);
|
||||
}
|
Reference in New Issue
Block a user