From 06f1cc3ca19af649c428e7dd0489e5a3a853151c Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 11:12:37 +0800 Subject: [PATCH] :recycle: Remix things up --- DysonNetwork.Drive/Program.cs | 7 +- .../Startup/ApplicationBuilderExtensions.cs | 11 ++- .../Startup/ServiceCollectionExtensions.cs | 17 ++++- .../Storage/FileReferenceServiceGrpc.cs | 6 -- DysonNetwork.Drive/Storage/FileServiceGrpc.cs | 1 - .../Account/AccountEventService.cs | 22 ++++-- DysonNetwork.Pass/Program.cs | 1 + DysonNetwork.Shared/Proto/file.proto | 70 +++++++++---------- DysonNetwork.Shared/Registry/ServiceHelper.cs | 8 +-- 9 files changed, 86 insertions(+), 57 deletions(-) diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs index 480ed64..d538e61 100644 --- a/DysonNetwork.Drive/Program.cs +++ b/DysonNetwork.Drive/Program.cs @@ -4,6 +4,7 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; +using tusdotnet.Stores; var builder = WebApplication.CreateBuilder(args); @@ -18,6 +19,8 @@ builder.Services.AddAppAuthentication(); builder.Services.AddAppSwagger(); builder.Services.AddDysonAuth(builder.Configuration); +builder.Services.AddAppFileStorage(builder.Configuration); + // Add flush handlers and websocket handlers builder.Services.AddAppFlushHandlers(); @@ -36,8 +39,10 @@ using (var scope = app.Services.CreateScope()) await db.Database.MigrateAsync(); } +var tusDiskStore = app.Services.GetRequiredService(); + // Configure application middleware pipeline -app.ConfigureAppMiddleware(builder.Configuration); +app.ConfigureAppMiddleware(tusDiskStore); // Configure gRPC app.ConfigureGrpcServices(); diff --git a/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs index 7504fbe..698dabe 100644 --- a/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs +++ b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs @@ -1,8 +1,12 @@ +using DysonNetwork.Drive.Storage; +using tusdotnet; +using tusdotnet.Interfaces; + namespace DysonNetwork.Drive.Startup; public static class ApplicationBuilderExtensions { - public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration) + public static WebApplication ConfigureAppMiddleware(this WebApplication app, ITusStore tusStore) { // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) @@ -14,6 +18,8 @@ public static class ApplicationBuilderExtensions app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); + + app.MapTus("/tus", _ => Task.FromResult(TusService.BuildConfiguration(tusStore))); return app; } @@ -21,7 +27,8 @@ public static class ApplicationBuilderExtensions public static WebApplication ConfigureGrpcServices(this WebApplication app) { // Map your gRPC services here - // Example: app.MapGrpcService(); + app.MapGrpcService(); + app.MapGrpcService(); return app; } diff --git a/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs index fba75d1..c848be3 100644 --- a/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs @@ -1,6 +1,5 @@ using System.Text.Json; using System.Threading.RateLimiting; -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Cache; using Microsoft.AspNetCore.RateLimiting; using Microsoft.OpenApi.Models; @@ -8,6 +7,7 @@ using NodaTime; using NodaTime.Serialization.SystemTextJson; using StackExchange.Redis; using DysonNetwork.Shared.Proto; +using tusdotnet.Stores; namespace DysonNetwork.Drive.Startup; @@ -122,10 +122,23 @@ public static class ServiceCollectionExtensions return services; } + + public static IServiceCollection AddAppFileStorage(this IServiceCollection services, IConfiguration configuration) + { + var tusStorePath = configuration.GetSection("Tus").GetValue("StorePath")!; + Directory.CreateDirectory(tusStorePath); + var tusDiskStore = new TusDiskStore(tusStorePath); + + services.AddSingleton(tusDiskStore); + + return services; + } public static IServiceCollection AddAppBusinessServices(this IServiceCollection services) { - // Add your business services here + services.AddScoped(); + services.AddScoped(); + return services; } } \ No newline at end of file diff --git a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs index 758efe2..d31a8c3 100644 --- a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs @@ -1,6 +1,4 @@ -using System.Threading.Tasks; using DysonNetwork.Shared.Proto; -using Google.Protobuf.WellKnownTypes; using Grpc.Core; using NodaTime; using Duration = NodaTime.Duration; @@ -13,13 +11,9 @@ namespace DysonNetwork.Drive.Storage { 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, diff --git a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs index ea1e735..ef80f5c 100644 --- a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using DysonNetwork.Shared.Proto; using Google.Protobuf.WellKnownTypes; using Grpc.Core; diff --git a/DysonNetwork.Pass/Account/AccountEventService.cs b/DysonNetwork.Pass/Account/AccountEventService.cs index 234b29b..5fe756b 100644 --- a/DysonNetwork.Pass/Account/AccountEventService.cs +++ b/DysonNetwork.Pass/Account/AccountEventService.cs @@ -1,6 +1,7 @@ using System.Globalization; using DysonNetwork.Pass.Wallet; using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Proto; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using NodaTime; @@ -11,11 +12,20 @@ public class AccountEventService( AppDatabase db, PaymentService payment, ICacheService cache, - IStringLocalizer localizer + IStringLocalizer localizer, + PusherService.PusherServiceClient pusher ) { private static readonly Random Random = new(); - private const string StatusCacheKey = "AccountStatus_"; + private const string StatusCacheKey = "account:status:"; + + private async Task GetAccountIsConnected(Guid userId) + { + var resp = await pusher.GetWebsocketConnectionStatusAsync( + new GetWebsocketConnectionStatusRequest { UserId = userId.ToString() } + ); + return resp.IsConnected; + } public void PurgeStatusCache(Guid userId) { @@ -29,7 +39,7 @@ public class AccountEventService( var cachedStatus = await cache.GetAsync(cacheKey); if (cachedStatus is not null) { - cachedStatus!.IsOnline = !cachedStatus.IsInvisible; // && ws.GetAccountIsConnected(userId); + cachedStatus!.IsOnline = !cachedStatus.IsInvisible && await GetAccountIsConnected(userId); return cachedStatus; } @@ -81,7 +91,7 @@ public class AccountEventService( var cachedStatus = await cache.GetAsync(cacheKey); if (cachedStatus != null) { - cachedStatus.IsOnline = !cachedStatus.IsInvisible /* && ws.GetAccountIsConnected(userId) */; + cachedStatus.IsOnline = !cachedStatus.IsInvisible && await GetAccountIsConnected(userId); results[userId] = cachedStatus; } else @@ -104,7 +114,7 @@ public class AccountEventService( foreach (var status in statusesFromDb) { - var isOnline = false; // ws.GetAccountIsConnected(status.AccountId); + var isOnline = await GetAccountIsConnected(status.AccountId); status.IsOnline = !status.IsInvisible && isOnline; results[status.AccountId] = status; var cacheKey = $"{StatusCacheKey}{status.AccountId}"; @@ -117,7 +127,7 @@ public class AccountEventService( { foreach (var userId in usersWithoutStatus) { - var isOnline = false; // ws.GetAccountIsConnected(userId); + var isOnline = await GetAccountIsConnected(userId); var defaultStatus = new Status { Attitude = StatusAttitude.Neutral, diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 1dfa7d2..56c449e 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -15,6 +15,7 @@ builder.Services.AddAppMetrics(); // Add application services builder.Services.AddRegistryService(builder.Configuration); +builder.Services.AddPusherService(); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); diff --git a/DysonNetwork.Shared/Proto/file.proto b/DysonNetwork.Shared/Proto/file.proto index 92cd6f7..515e16f 100644 --- a/DysonNetwork.Shared/Proto/file.proto +++ b/DysonNetwork.Shared/Proto/file.proto @@ -15,34 +15,34 @@ import "google/protobuf/duration.proto"; message CloudFile { // Unique identifier for the file string id = 1; - + // Original name of the file string name = 2; - + // File metadata (e.g., dimensions, duration, etc.) map file_meta = 3; - + // User-defined metadata map user_meta = 4; - + // MIME type of the file string mime_type = 5; - + // File content hash (e.g., MD5, SHA-256) string hash = 6; - + // File size in bytes int64 size = 7; - + // Indicates if the file is stored with compression bool has_compression = 8; - + // URL to access the file string url = 9; - + // Content type of the file string content_type = 10; - + // When the file was uploaded google.protobuf.Timestamp uploaded_at = 11; } @@ -51,28 +51,28 @@ message CloudFile { service FileService { // Get file reference by ID rpc GetFile(GetFileRequest) returns (CloudFile); - + // Update an existing file reference rpc UpdateFile(UpdateFileRequest) returns (CloudFile); - + // Delete a file reference rpc DeleteFile(DeleteFileRequest) returns (google.protobuf.Empty); - + // Process and upload a new file rpc ProcessNewFile(stream ProcessNewFileRequest) returns (CloudFile); - + // Upload a file to remote storage rpc UploadFileToRemote(stream UploadFileToRemoteRequest) returns (CloudFile); - + // Delete file data from storage rpc DeleteFileData(DeleteFileDataRequest) returns (google.protobuf.Empty); - + // Load files from references rpc LoadFromReference(LoadFromReferenceRequest) returns (LoadFromReferenceResponse); - + // Check if a file is referenced by any resource rpc IsReferenced(IsReferencedRequest) returns (IsReferencedResponse); - + // Purge cache for a file rpc PurgeCache(PurgeCacheRequest) returns (google.protobuf.Empty); } @@ -160,19 +160,19 @@ message PurgeCacheRequest { message CloudFileReference { // Unique identifier for the reference string id = 1; - + // Reference to the actual file string file_id = 2; - + // The actual file data (optional, can be populated when needed) CloudFile file = 3; - + // Description of how this file is being used string usage = 4; - + // ID of the resource that this file is associated with string resource_id = 5; - + // Optional expiration timestamp for the reference google.protobuf.Timestamp expired_at = 6; } @@ -182,8 +182,8 @@ message CreateReferenceRequest { string file_id = 1; string usage = 2; string resource_id = 3; - google.protobuf.Timestamp expired_at = 4; - google.protobuf.Duration duration = 5; // Alternative to expired_at + optional google.protobuf.Timestamp expired_at = 4; + optional google.protobuf.Duration duration = 5; // Alternative to expired_at } message GetReferencesRequest { @@ -268,34 +268,34 @@ message HasFileReferencesResponse { service FileReferenceService { // Creates a new reference to a file for a specific resource rpc CreateReference(CreateReferenceRequest) returns (CloudFileReference); - + // Gets all references to a file rpc GetReferences(GetReferencesRequest) returns (GetReferencesResponse); - + // Gets the number of references to a file rpc GetReferenceCount(GetReferenceCountRequest) returns (GetReferenceCountResponse); - + // Gets all references for a specific resource and optional usage rpc GetResourceReferences(GetResourceReferencesRequest) returns (GetReferencesResponse); - + // Gets all files referenced by a resource with optional usage filter rpc GetResourceFiles(GetResourceFilesRequest) returns (GetResourceFilesResponse); - + // Deletes references for a specific resource and optional usage rpc DeleteResourceReferences(DeleteResourceReferencesRequest) returns (DeleteResourceReferencesResponse); - + // Deletes a specific file reference rpc DeleteReference(DeleteReferenceRequest) returns (DeleteReferenceResponse); - + // Updates the files referenced by a resource rpc UpdateResourceFiles(UpdateResourceFilesRequest) returns (UpdateResourceFilesResponse); - + // Updates the expiration time for a file reference rpc SetReferenceExpiration(SetReferenceExpirationRequest) returns (SetReferenceExpirationResponse); - + // Updates the expiration time for all references to a file rpc SetFileReferencesExpiration(SetFileReferencesExpirationRequest) returns (SetFileReferencesExpirationResponse); - + // Checks if a file has any references rpc HasFileReferences(HasFileReferencesRequest) returns (HasFileReferencesResponse); } diff --git a/DysonNetwork.Shared/Registry/ServiceHelper.cs b/DysonNetwork.Shared/Registry/ServiceHelper.cs index 47353ee..6b2f18b 100644 --- a/DysonNetwork.Shared/Registry/ServiceHelper.cs +++ b/DysonNetwork.Shared/Registry/ServiceHelper.cs @@ -13,8 +13,8 @@ public static class ServiceHelper { var etcdClient = sp.GetRequiredService(); var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]; - var clientKeyPath = config["Service:ClientKey"]; + var clientCertPath = config["Service:ClientCert"]!; + var clientKeyPath = config["Service:ClientKey"]!; var clientCertPassword = config["Service:CertPassword"]; return GrpcClientHelper @@ -32,8 +32,8 @@ public static class ServiceHelper { var etcdClient = sp.GetRequiredService(); var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]; - var clientKeyPath = config["Service:ClientKey"]; + var clientCertPath = config["Service:ClientCert"]!; + var clientKeyPath = config["Service:ClientKey"]!; var clientCertPassword = config["Service:CertPassword"]; return GrpcClientHelper