Dreamine.Web 1.0.0.0
WPF와 Blazor를 한 코드 흐름으로 연결하고 반복적인 MVVM 코드를 줄이는 오픈소스 FullKit 공식 웹 애플리케이션입니다.
로딩중...
검색중...
일치하는것 없음
LibraryCatalogSyncService.cs
이 파일의 문서화 페이지로 가기
1using System.IO;
2using System.Text.RegularExpressions;
3using System.Xml.Linq;
5
7
16public sealed class LibraryCatalogSyncService
17{
26 private readonly ILibraryStore _store;
35 private readonly string _libRoot;
44 private readonly string _xmlDocRoot;
45
54 private static readonly Regex Whitespace = new(@"\s+", RegexOptions.Compiled);
55
81 {
82 _store = store;
84 _xmlDocRoot = Path.Combine(AppContext.BaseDirectory, "wwwroot", "xmldocs");
85 }
86
95 public bool IsConfigured => !string.IsNullOrWhiteSpace(_libRoot) && Directory.Exists(_libRoot);
96
113 public async Task<LibraryCatalogSyncResult> SyncAsync()
114 {
115 if (!IsConfigured) return new LibraryCatalogSyncResult(-1, 0, 0);
116
117 var projects = Directory.EnumerateFiles(_libRoot, "*.csproj", SearchOption.AllDirectories)
118 .Select(ReadProject)
119 .Where(p => p is not null)
120 .Cast<ProjectMetadata>()
121 .Where(p => !p.PackageId.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase))
122 .OrderBy(p => CategoryRank(p.Category))
123 .ThenBy(p => p.PackageId, StringComparer.OrdinalIgnoreCase)
124 .ToList();
125
126 var libraries = await _store.GetAllAsync();
127 var byName = libraries.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
128 var nextSortByCategory = libraries
129 .GroupBy(x => string.IsNullOrWhiteSpace(x.Category) ? "Other" : x.Category)
130 .ToDictionary(g => g.Key, g => g.Max(x => x.SortOrder), StringComparer.OrdinalIgnoreCase);
131
132 var added = 0;
133 var updated = 0;
134
135 foreach (var project in projects)
136 {
137 var isNew = !byName.TryGetValue(project.PackageId, out var lib);
138 if (isNew)
139 {
140 var sortBase = nextSortByCategory.TryGetValue(project.Category, out var current)
141 ? current
142 : CategoryRank(project.Category) * 10;
143
144 lib = new LibraryInfo
145 {
146 Id = ToId(project.PackageId),
147 Name = project.PackageId,
148 Category = project.Category,
149 SortOrder = sortBase + 1,
150 IsVisible = true,
151 Status = "stable",
152 Version = "1.0.0"
153 };
154
155 nextSortByCategory[project.Category] = lib.SortOrder;
156 added++;
157 }
158
159 if (ApplyProjectMetadata(lib!, project) || isNew)
160 {
161 await _store.SaveAsync(lib!);
162 if (!isNew) updated++;
163 }
164
165 byName[project.PackageId] = lib!;
166 }
167
168 return new LibraryCatalogSyncResult(added, updated, projects.Count);
169 }
170
204 {
205 var dirty = false;
206
207 if (SetString(lib.Name, project.PackageId, v => lib.Name = v ?? string.Empty)) dirty = true;
208 if (SetString(lib.Category, project.Category, v => lib.Category = v ?? string.Empty)) dirty = true;
209 if (SetString(lib.Version, project.Version, v => lib.Version = v ?? string.Empty)) dirty = true;
210 if (SetString(lib.NuGetId, project.PackageId, v => lib.NuGetId = v)) dirty = true;
211 if (SetString(lib.SourceProjectPath, project.ProjectPath, v => lib.SourceProjectPath = v)) dirty = true;
212 if (SetString(lib.TargetFramework, project.TargetFramework, v => lib.TargetFramework = v)) dirty = true;
213
214 if (!string.IsNullOrWhiteSpace(project.Description))
215 {
216 if (SetString(lib.DescriptionEn, project.Description, v => lib.DescriptionEn = v)) dirty = true;
217 if (string.IsNullOrWhiteSpace(lib.Description))
218 {
219 lib.Description = project.Description;
220 dirty = true;
221 }
222 }
223
224 if (!string.IsNullOrWhiteSpace(project.RepositoryUrl))
225 {
226 if (SetString(lib.RepoUrl, project.RepositoryUrl, v => lib.RepoUrl = v)) dirty = true;
227 }
228
229 if (project.Tags.Length > 0 && !SameSet(lib.Tags, project.Tags))
230 {
231 lib.Tags = project.Tags;
232 dirty = true;
233 }
234
235 if (!SameSet(lib.Dependencies, project.Dependencies))
236 {
237 lib.Dependencies = project.Dependencies;
238 dirty = true;
239 }
240
241 var xmlPath = Path.Combine(_xmlDocRoot, project.PackageId, $"{project.PackageId}.xml");
242 if (File.Exists(xmlPath))
243 {
244 if (SetString(lib.XmlDocPath, xmlPath, v => lib.XmlDocPath = v)) dirty = true;
245 }
246
247 if (dirty) lib.UpdatedAt = DateTime.UtcNow;
248 return dirty;
249 }
250
275 private static ProjectMetadata? ReadProject(string path)
276 {
277 try
278 {
279 var doc = XDocument.Load(path);
280 var root = doc.Root;
281 if (root is null) return null;
282
283 #pragma warning disable CS1587
310 string? Property(string name) => root.Elements("PropertyGroup")
311 .Elements(name)
312 .Select(x => x.Value.Trim())
313 .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x));
314 #pragma warning restore CS1587
315
316 var assemblyName = Path.GetFileNameWithoutExtension(path);
317 var packageId = Property("PackageId") ?? Property("AssemblyName") ?? assemblyName;
318 var version = Property("Version");
319 if (string.IsNullOrWhiteSpace(version) || version.Contains("$(", StringComparison.Ordinal))
320 version = "1.0.0";
321
322 var targetFramework = Property("TargetFramework") ?? Property("TargetFrameworks") ?? string.Empty;
323 var description = Normalize(Property("Description"));
324 var tags = SplitTags(Property("PackageTags"));
325 var dependencies = root.Elements("ItemGroup")
326 .Elements("ProjectReference")
327 .Select(x => x.Attribute("Include")?.Value)
328 .Where(x => !string.IsNullOrWhiteSpace(x))
329 .Select(x => Path.GetFileNameWithoutExtension(x!))
330 .Where(x => !string.IsNullOrWhiteSpace(x))
331 .Distinct(StringComparer.OrdinalIgnoreCase)
332 .OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
333 .ToArray();
334
335 return new ProjectMetadata(
336 path,
337 packageId,
338 version,
339 targetFramework,
340 description,
341 Property("RepositoryUrl") ?? Property("PackageProjectUrl"),
342 tags,
343 dependencies,
344 InferCategory(packageId));
345 }
346 catch
347 {
348 return null;
349 }
350 }
351
376 private static string InferCategory(string packageId)
377 {
378 if (packageId.StartsWith("Dreamine.MVVM.", StringComparison.OrdinalIgnoreCase)) return "MVVM";
379 if (packageId.StartsWith("Dreamine.UI.", StringComparison.OrdinalIgnoreCase)) return "UI";
380 if (packageId.StartsWith("Dreamine.Communication.", StringComparison.OrdinalIgnoreCase)) return "Communication";
381 if (packageId.StartsWith("Dreamine.Database.", StringComparison.OrdinalIgnoreCase)) return "Database";
382 if (packageId.StartsWith("Dreamine.PLC.", StringComparison.OrdinalIgnoreCase)) return "PLC";
383 if (packageId.StartsWith("Dreamine.IO.", StringComparison.OrdinalIgnoreCase)) return "IO";
384 if (packageId.StartsWith("Dreamine.Threading", StringComparison.OrdinalIgnoreCase)) return "Infrastructure";
385 if (packageId.StartsWith("Dreamine.Logging", StringComparison.OrdinalIgnoreCase)) return "Infrastructure";
386 if (packageId.StartsWith("Dreamine.Hybrid", StringComparison.OrdinalIgnoreCase)) return "Hybrid";
387 if (packageId.StartsWith("Dreamine.Identity", StringComparison.OrdinalIgnoreCase)) return "Identity";
388 return "Other";
389 }
390
415 private static int CategoryRank(string category) => category switch
416 {
417 "MVVM" => 1,
418 "Hybrid" => 2,
419 "UI" => 3,
420 "Identity" => 4,
421 "Infrastructure" => 5,
422 "Database" => 6,
423 "Communication" => 7,
424 "IO" => 8,
425 "PLC" => 9,
426 _ => 99
427 };
428
453 private static string ToId(string packageId)
454 {
455 var id = packageId.StartsWith("Dreamine.", StringComparison.OrdinalIgnoreCase)
456 ? packageId["Dreamine.".Length..]
457 : packageId;
458
459 return Regex.Replace(id.ToLowerInvariant(), @"[^a-z0-9]+", "-").Trim('-');
460 }
461
486 private static string? Normalize(string? value)
487 {
488 if (string.IsNullOrWhiteSpace(value)) return null;
489 return Whitespace.Replace(value.Trim(), " ");
490 }
491
516 private static string[] SplitTags(string? tags) =>
517 string.IsNullOrWhiteSpace(tags)
518 ? []
519 : tags.Split([';', ','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
520 .Distinct(StringComparer.OrdinalIgnoreCase)
521 .OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
522 .ToArray();
523
556 private static bool SameSet(string[] left, string[] right) =>
557 left.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
558 .SequenceEqual(right.OrderBy(x => x, StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase);
559
600 private static bool SetString(string? current, string? value, Action<string?> assign)
601 {
602 value = string.IsNullOrWhiteSpace(value) ? null : value;
603 if (string.Equals(current, value, StringComparison.Ordinal)) return false;
604 assign(value);
605 return true;
606 }
607
616 private sealed record ProjectMetadata(
617 string ProjectPath,
618 string PackageId,
619 string Version,
620 string TargetFramework,
621 string? Description,
622 string? RepositoryUrl,
623 string[] Tags,
624 string[] Dependencies,
625 string Category);
626}
627
636public sealed record LibraryCatalogSyncResult(int Added, int Updated, int ScannedProjects);
record LibraryCatalogSyncResult(int Added, int Updated, int ScannedProjects)
static bool SameSet(string[] left, string[] right)
static bool SetString(string? current, string? value, Action< string?> assign)
LibraryCatalogSyncService(ILibraryStore store, DreamineOptions opts)
bool ApplyProjectMetadata(LibraryInfo lib, ProjectMetadata project)
record ProjectMetadata(string ProjectPath, string PackageId, string Version, string TargetFramework, string? Description, string? RepositoryUrl, string[] Tags, string[] Dependencies, string Category)
async Task< LibraryCatalogSyncResult > SyncAsync()
static ? ProjectMetadata ReadProject(string path)