using MemoryPack; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; namespace AssetDependencyGraph { [BsonIgnoreExtraElements] [MemoryPackable] public partial class AssetIdentify { public string Path = null!; public string AssetType = null!; public string Guid; public string Md5; } public sealed class AssetIdentifyJsonConverter : JsonConverter { static JsonSerializerOptions serializerOptions = new JsonSerializerOptions() { IncludeFields = true }; public override AssetIdentify? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return JsonSerializer.Deserialize(reader.GetString()!, serializerOptions); } public override void Write(Utf8JsonWriter writer, AssetIdentify value, JsonSerializerOptions options) { writer.WriteStringValue(JsonSerializer.Serialize(value, serializerOptions)); } public override AssetIdentify ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return Read(ref reader, typeToConvert, serializerOptions)!; } public override void WriteAsPropertyName(Utf8JsonWriter writer, [DisallowNull] AssetIdentify value, JsonSerializerOptions options) { writer.WritePropertyName(JsonSerializer.Serialize(value, serializerOptions)); } } [BsonIgnoreExtraElements] [MemoryPackable] public partial class AssetNode { public AssetIdentify Self = null!; public string AssetType = null!; [JsonIgnore] [MemoryPackIgnore] public ConcurrentBag Dependencies = new(); [JsonIgnore] [MemoryPackIgnore] public ConcurrentBag Dependent = new(); [AllowNull] public HashSet DependencySet; [AllowNull] public HashSet DependentSet; } public sealed class AssetNodeJsonConverter : JsonConverter { static JsonSerializerOptions serializerOptions = new JsonSerializerOptions() { IncludeFields = true, Converters = { new AssetIdentifyJsonConverter() } }; public override AssetNode? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return JsonSerializer.Deserialize(reader.GetString()!, serializerOptions); } public override void Write(Utf8JsonWriter writer, AssetNode value, JsonSerializerOptions options) { writer.WriteStringValue(JsonSerializer.Serialize(value, serializerOptions)); } } [BsonIgnoreExtraElements] public sealed class FolderNode : AssetNode { } [BsonIgnoreExtraElements] public sealed class PackageNode : AssetNode { } public class AssetDependencyGraphDB { MongoClient client; IMongoCollection FolderNodes; IMongoCollection PackageNodes; IMongoCollection AssetNodes; Dictionary findCacheDic = new(); public AssetDependencyGraphDB(string user, string passwd, string ip) { MongoClientSettings settings; if (string.IsNullOrWhiteSpace(user) && !string.IsNullOrEmpty(ip)) { settings = MongoClientSettings.FromUrl(new MongoUrl($"mongodb://{ip}:27017/")); } else { settings = MongoClientSettings.FromUrl(new MongoUrl($"mongodb://{user}:{passwd}@{ip}:27017/")); } settings.ConnectTimeout = TimeSpan.FromSeconds(5); settings.MinConnectionPoolSize = 1; settings.MaxConnectionPoolSize = 25; client = new MongoClient(settings); var db = client.GetDatabase("assetgraph"); FolderNodes = db.GetCollection("folder_nodes"); PackageNodes = db.GetCollection("package_nodes"); AssetNodes = db.GetCollection("asset_nodes"); } public void Clean() { client.DropDatabase("assetgraph"); var db = client.GetDatabase("assetgraph"); FolderNodes = db.GetCollection("folder_nodes"); PackageNodes = db.GetCollection("package_nodes"); AssetNodes = db.GetCollection("asset_nodes"); } public void Insert(T node) where T : AssetNode { switch (node) { case FolderNode folderNode: { FolderNodes.InsertOne(folderNode); break; } case PackageNode packageNode: { PackageNodes.InsertOne(packageNode); break; } case AssetNode assetNode: { AssetNodes.InsertOne(assetNode); break; } default: break; } } public void UpdateOrInsert(T node) where T : AssetNode { switch (node) { case FolderNode folderNode: { var filter = Builders.Filter.And( Builders.Filter.Eq(fn => fn.Self.Path, node.Self.Path) ); var found = FolderNodes.Find(filter); if (found == null || found.CountDocuments() == 0) { FolderNodes.InsertOne(folderNode); } else { var result = FolderNodes.UpdateOne(filter, Builders.Update.Combine( Builders.Update.Set(fn => fn.Self, folderNode.Self), Builders.Update.Set(fn => fn.AssetType, folderNode.AssetType), Builders.Update.Set(fn => fn.Dependencies, folderNode.Dependencies), Builders.Update.Set(fn => fn.Dependent, folderNode.Dependent) )); } break; } case PackageNode packageNode: { var filter = Builders.Filter.And( Builders.Filter.Eq(fn => fn.Self.Path, node.Self.Path) ); var found = PackageNodes.Find(filter); if (found == null || found.CountDocuments() == 0) { PackageNodes.InsertOne(packageNode); } else { var result = PackageNodes.UpdateOne(filter, Builders.Update.Combine( Builders.Update.Set(fn => fn.Self, packageNode.Self), Builders.Update.Set(fn => fn.AssetType, packageNode.AssetType), Builders.Update.Set(fn => fn.Dependencies, packageNode.Dependencies), Builders.Update.Set(fn => fn.Dependent, packageNode.Dependent) )); } break; } case AssetNode assetNode: { var filter = Builders.Filter.And( Builders.Filter.Eq(fn => fn.Self.Path, node.Self.Path) ); var found = AssetNodes.Find(filter); if (found == null || found.CountDocuments() == 0) { AssetNodes.InsertOne(assetNode); } else { var result = AssetNodes.UpdateOne(filter, Builders.Update.Combine( Builders.Update.Set(fn => fn.Self, assetNode.Self), Builders.Update.Set(fn => fn.AssetType, assetNode.AssetType), Builders.Update.Set(fn => fn.Dependencies, assetNode.Dependencies), Builders.Update.Set(fn => fn.Dependent, assetNode.Dependent) )); } break; } default: break; } } public void Delete(T node) where T : AssetNode { switch (node) { case FolderNode folderNode: { var filter = Builders.Filter.And( Builders.Filter.Eq(fn => fn.Self.Path, node.Self.Path) ); var found = FolderNodes.Find(filter); if (found != null && found.CountDocuments() == 0) { // TODO: del ref dep FolderNodes.DeleteOne(filter); } break; } case PackageNode packageNode: { var filter = Builders.Filter.And( Builders.Filter.Eq(fn => fn.Self.Path, node.Self.Path) ); var found = PackageNodes.Find(filter); if (found != null && found.CountDocuments() == 0) { // TODO: del ref dep PackageNodes.DeleteOne(filter); } break; } case AssetNode assetNode: { var filter = Builders.Filter.And( Builders.Filter.Eq(fn => fn.Self.Path, node.Self.Path) ); var found = AssetNodes.Find(filter); if (found != null && found.CountDocuments() == 0) { // TODO: del ref dep AssetNodes.DeleteOne(filter); } break; } default: break; } } public AssetNode Find(string path) { if (findCacheDic.TryGetValue(path, out var assetNode)) { return assetNode; } var filter = Builders.Filter.And( Builders.Filter.Eq(fn => fn.Self.Path, path) ); var found = AssetNodes.Find(filter); if (found != null && found.CountDocuments() != 0) { assetNode = found.First(); findCacheDic[path] = assetNode; return assetNode; } var filter1 = Builders.Filter.And( Builders.Filter.Eq(fn => fn.Self.Path, path) ); var found1 = PackageNodes.Find(filter1); if (found1 != null && found1.CountDocuments() != 0) { assetNode = found1.First(); findCacheDic[path] = assetNode; return assetNode; } var filter2 = Builders.Filter.And( Builders.Filter.Eq(fn => fn.Self.Path, path) ); var found2 = FolderNodes.Find(filter2); if (found2 != null && found2.CountDocuments() != 0) { assetNode = found2.First(); findCacheDic[path] = assetNode; return assetNode; } return null!; } } }