diff --git a/DysonNetwork.Drive/DysonNetwork.Drive.csproj b/DysonNetwork.Drive/DysonNetwork.Drive.csproj
index 19bfcab..0543986 100644
--- a/DysonNetwork.Drive/DysonNetwork.Drive.csproj
+++ b/DysonNetwork.Drive/DysonNetwork.Drive.csproj
@@ -66,9 +66,4 @@
-
-
-
-
-
diff --git a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs
new file mode 100644
index 0000000..758efe2
--- /dev/null
+++ b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs
@@ -0,0 +1,127 @@
+using System.Threading.Tasks;
+using DysonNetwork.Shared.Proto;
+using Google.Protobuf.WellKnownTypes;
+using Grpc.Core;
+using NodaTime;
+using Duration = NodaTime.Duration;
+
+namespace DysonNetwork.Drive.Storage
+{
+ public class FileReferenceServiceGrpc(FileReferenceService fileReferenceService) : Shared.Proto.FileReferenceService.FileReferenceServiceBase
+ {
+ public override async Task CreateReference(CreateReferenceRequest request, ServerCallContext context)
+ {
+ Instant? expiredAt = null;
+ if (request.ExpiredAt != null)
+ {
+ expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds);
+ }
+ else if (request.Duration != null)
+ {
+ expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan());
+ }
+
+ var reference = await fileReferenceService.CreateReferenceAsync(
+ request.FileId,
+ request.Usage,
+ request.ResourceId,
+ expiredAt
+ );
+ return reference.ToProtoValue();
+ }
+
+ public override async Task GetReferences(GetReferencesRequest request, ServerCallContext context)
+ {
+ var references = await fileReferenceService.GetReferencesAsync(request.FileId);
+ var response = new GetReferencesResponse();
+ response.References.AddRange(references.Select(r => r.ToProtoValue()));
+ return response;
+ }
+
+ public override async Task GetReferenceCount(GetReferenceCountRequest request, ServerCallContext context)
+ {
+ var count = await fileReferenceService.GetReferenceCountAsync(request.FileId);
+ return new GetReferenceCountResponse { Count = count };
+ }
+
+ public override async Task GetResourceReferences(GetResourceReferencesRequest request, ServerCallContext context)
+ {
+ var references = await fileReferenceService.GetResourceReferencesAsync(request.ResourceId, request.Usage);
+ var response = new GetReferencesResponse();
+ response.References.AddRange(references.Select(r => r.ToProtoValue()));
+ return response;
+ }
+
+ public override async Task GetResourceFiles(GetResourceFilesRequest request, ServerCallContext context)
+ {
+ var files = await fileReferenceService.GetResourceFilesAsync(request.ResourceId, request.Usage);
+ var response = new GetResourceFilesResponse();
+ response.Files.AddRange(files.Select(f => f.ToProtoValue()));
+ return response;
+ }
+
+ public override async Task DeleteResourceReferences(DeleteResourceReferencesRequest request, ServerCallContext context)
+ {
+ var deletedCount = await fileReferenceService.DeleteResourceReferencesAsync(request.ResourceId, request.Usage);
+ return new DeleteResourceReferencesResponse { DeletedCount = deletedCount };
+ }
+
+ public override async Task DeleteReference(DeleteReferenceRequest request, ServerCallContext context)
+ {
+ var success = await fileReferenceService.DeleteReferenceAsync(Guid.Parse(request.ReferenceId));
+ return new DeleteReferenceResponse { Success = success };
+ }
+
+ public override async Task UpdateResourceFiles(UpdateResourceFilesRequest request, ServerCallContext context)
+ {
+ Instant? expiredAt = null;
+ if (request.ExpiredAt != null)
+ {
+ expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds);
+ }
+ else if (request.Duration != null)
+ {
+ expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan());
+ }
+
+ var references = await fileReferenceService.UpdateResourceFilesAsync(
+ request.ResourceId,
+ request.FileIds,
+ request.Usage,
+ expiredAt
+ );
+ var response = new UpdateResourceFilesResponse();
+ response.References.AddRange(references.Select(r => r.ToProtoValue()));
+ return response;
+ }
+
+ public override async Task SetReferenceExpiration(SetReferenceExpirationRequest request, ServerCallContext context)
+ {
+ Instant? expiredAt = null;
+ if (request.ExpiredAt != null)
+ {
+ expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds);
+ }
+ else if (request.Duration != null)
+ {
+ expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan());
+ }
+
+ var success = await fileReferenceService.SetReferenceExpirationAsync(Guid.Parse(request.ReferenceId), expiredAt);
+ return new SetReferenceExpirationResponse { Success = success };
+ }
+
+ public override async Task SetFileReferencesExpiration(SetFileReferencesExpirationRequest request, ServerCallContext context)
+ {
+ var expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds);
+ var updatedCount = await fileReferenceService.SetFileReferencesExpirationAsync(request.FileId, expiredAt);
+ return new SetFileReferencesExpirationResponse { UpdatedCount = updatedCount };
+ }
+
+ public override async Task HasFileReferences(HasFileReferencesRequest request, ServerCallContext context)
+ {
+ var hasReferences = await fileReferenceService.HasFileReferencesAsync(request.FileId);
+ return new HasFileReferencesResponse { HasReferences = hasReferences };
+ }
+ }
+}
\ No newline at end of file
diff --git a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs
new file mode 100644
index 0000000..d3118a5
--- /dev/null
+++ b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs
@@ -0,0 +1,175 @@
+using System.Threading.Tasks;
+using DysonNetwork.Shared.Proto;
+using Google.Protobuf.WellKnownTypes;
+using Grpc.Core;
+
+namespace DysonNetwork.Drive.Storage
+{
+ public class FileServiceGrpc(FileService fileService) : Shared.Proto.FileService.FileServiceBase
+ {
+ public override async Task GetFile(GetFileRequest request, ServerCallContext context)
+ {
+ var file = await fileService.GetFileAsync(request.Id);
+ return file?.ToProtoValue() ?? throw new RpcException(new Status(StatusCode.NotFound, "File not found"));
+ }
+
+ public override async Task UpdateFile(UpdateFileRequest request, ServerCallContext context)
+ {
+ // Assuming UpdateFileAsync exists in FileService and handles the update_mask
+ // This is a placeholder, as the current FileService.cs doesn't have a direct UpdateFile method
+ // You might need to implement this logic in FileService based on your needs.
+ // For now, we'll just return the requested file.
+ var file = await fileService.GetFileAsync(request.File.Id);
+ if (file == null)
+ {
+ throw new RpcException(new Status(StatusCode.NotFound, "File not found"));
+ }
+
+ // Apply updates from request.File to 'file' based on request.UpdateMask
+ // This part requires more detailed implementation based on how you want to handle partial updates.
+ return file.ToProtoValue();
+ }
+
+ public override async Task DeleteFile(DeleteFileRequest request, ServerCallContext context)
+ {
+ var file = await fileService.GetFileAsync(request.Id);
+ if (file == null)
+ {
+ throw new RpcException(new Status(StatusCode.NotFound, "File not found"));
+ }
+
+ await fileService.DeleteFileAsync(file);
+ return new Empty();
+ }
+
+ public override async Task ProcessNewFile(IAsyncStreamReader requestStream,
+ ServerCallContext context)
+ {
+ ProcessNewFileRequest? metadataRequest = null;
+ var chunks = new List();
+
+ await foreach (var message in requestStream.ReadAllAsync())
+ {
+ if (message.DataCase == ProcessNewFileRequest.DataOneofCase.Metadata)
+ {
+ metadataRequest = message;
+ }
+ else if (message.DataCase == ProcessNewFileRequest.DataOneofCase.Chunk)
+ {
+ chunks.Add(message.Chunk.ToByteArray());
+ }
+ }
+
+ if (metadataRequest == null || metadataRequest.Metadata == null)
+ {
+ throw new RpcException(new Status(StatusCode.InvalidArgument, "Missing file metadata"));
+ }
+
+ var metadata = metadataRequest.Metadata;
+ using var memoryStream = new MemoryStream();
+ foreach (var chunk in chunks)
+ {
+ await memoryStream.WriteAsync(chunk);
+ }
+
+ memoryStream.Position = 0;
+
+ // Assuming you have an Account object available or can create a dummy one for now
+ // You might need to adjust this based on how accounts are handled in your system
+ var dummyAccount = new Account { Id = metadata.AccountId };
+
+ var cloudFile = await fileService.ProcessNewFileAsync(
+ dummyAccount,
+ metadata.FileId,
+ memoryStream,
+ metadata.FileName,
+ metadata.ContentType
+ );
+ return cloudFile.ToProtoValue();
+ }
+
+ public override async Task UploadFileToRemote(
+ IAsyncStreamReader requestStream, ServerCallContext context)
+ {
+ UploadFileToRemoteRequest? metadataRequest = null;
+ var chunks = new List();
+
+ await foreach (var message in requestStream.ReadAllAsync())
+ {
+ if (message.DataCase == UploadFileToRemoteRequest.DataOneofCase.Metadata)
+ {
+ metadataRequest = message;
+ }
+ else if (message.DataCase == UploadFileToRemoteRequest.DataOneofCase.Chunk)
+ {
+ chunks.Add(message.Chunk.ToByteArray());
+ }
+ }
+
+ if (metadataRequest == null || metadataRequest.Metadata == null)
+ {
+ throw new RpcException(new Status(StatusCode.InvalidArgument, "Missing upload metadata"));
+ }
+
+ var metadata = metadataRequest.Metadata;
+ using var memoryStream = new MemoryStream();
+ foreach (var chunk in chunks)
+ {
+ await memoryStream.WriteAsync(chunk);
+ }
+
+ memoryStream.Position = 0;
+
+ var file = await fileService.GetFileAsync(metadata.FileId);
+ if (file == null)
+ {
+ throw new RpcException(new Status(StatusCode.NotFound, "File not found"));
+ }
+
+ var uploadedFile = await fileService.UploadFileToRemoteAsync(
+ file,
+ memoryStream,
+ metadata.TargetRemote,
+ metadata.Suffix
+ );
+ return uploadedFile.ToProtoValue();
+ }
+
+ public override async Task DeleteFileData(DeleteFileDataRequest request, ServerCallContext context)
+ {
+ var file = await fileService.GetFileAsync(request.FileId);
+ if (file == null)
+ {
+ throw new RpcException(new Status(StatusCode.NotFound, "File not found"));
+ }
+
+ await fileService.DeleteFileDataAsync(file);
+ return new Empty();
+ }
+
+ public override async Task LoadFromReference(LoadFromReferenceRequest request,
+ ServerCallContext context)
+ {
+ // Assuming CloudFileReferenceObject is a simple class/struct that holds an ID
+ // You might need to define this or adjust the LoadFromReference method in FileService
+ var references = request.ReferenceIds.Select(id => new CloudFileReferenceObject { Id = id }).ToList();
+ var files = await fileService.LoadFromReference(references);
+ var response = new LoadFromReferenceResponse();
+ response.Files.AddRange(files.Where(f => f != null).Select(f => f!.ToProtoValue()));
+ return response;
+ }
+
+ public override async Task IsReferenced(IsReferencedRequest request,
+ ServerCallContext context)
+ {
+ var isReferenced = await fileService.IsReferencedAsync(request.FileId);
+ return new IsReferencedResponse { IsReferenced = isReferenced };
+ }
+
+ public override async Task PurgeCache(PurgeCacheRequest request, ServerCallContext context)
+ {
+ await fileService._PurgeCacheAsync(request.FileId);
+ return new Empty();
+ }
+ }
+}
\ No newline at end of file