Forge
csharpdeeb25a2
1using System.IO.Compression;
2using System.Security.Cryptography;
3
4namespace CascadeIDE.Services;
5
6/// <summary>Распаковка встроенного <c>kb-base-cide.zip</c> в кэш под <c>%LocalAppData%\CascadeIDE\kb-base-embedded-cache\{{sha256}}</c>.</summary>
7internal static class KbBaseEmbeddedBundleProvisioner
8{
9 internal const string ResourceName = "CascadeIDE.KbBase.kb-base-cide.zip";
10
11 private static readonly object Gate = new();
12 private static string? _resolvedCanonRoot;
13
14 /// <returns>Корень канона (родитель каталога <c>knowledge/</c>) или <c>null</c>, если ресурса нет / распаковка невозможна.</returns>
15 public static string? TryGetEmbeddedCanonRoot()
16 {
17 if (_resolvedCanonRoot is not null &&
18 Directory.Exists(Path.Combine(_resolvedCanonRoot, "knowledge")))
19 return _resolvedCanonRoot;
20
21 lock (Gate)
22 {
23 if (_resolvedCanonRoot is not null &&
24 Directory.Exists(Path.Combine(_resolvedCanonRoot, "knowledge")))
25 return _resolvedCanonRoot;
26
27 using var stream = typeof(KbBaseEmbeddedBundleProvisioner).Assembly.GetManifestResourceStream(ResourceName);
28 if (stream is null)
29 {
30 _resolvedCanonRoot = null;
31 return null;
32 }
33
34 byte[] zipBytes;
35 using (var ms = new MemoryStream())
36 {
37 stream.CopyTo(ms);
38 zipBytes = ms.ToArray();
39 }
40
41 var hash = Convert.ToHexString(SHA256.HashData(zipBytes)).ToLowerInvariant();
42 var extractRoot = Path.Combine(UserSettingsPaths.GetSettingsDirectory(), "kb-base-embedded-cache", hash);
43 var markerPath = Path.Combine(extractRoot, ".extracted-marker");
44
45 Directory.CreateDirectory(extractRoot);
46
47 var knowledgeDir = Path.Combine(extractRoot, "knowledge");
48 if (!File.Exists(markerPath) || !Directory.Exists(knowledgeDir))
49 {
50 TryClearExtractFolder(extractRoot);
51 Directory.CreateDirectory(extractRoot);
52
53 var tempZip = Path.Combine(extractRoot, "_bundle extracting.tmp.zip");
54 try
55 {
56 File.WriteAllBytes(tempZip, zipBytes);
57 ZipFile.ExtractToDirectory(tempZip, extractRoot, overwriteFiles: true);
58 }
59 finally
60 {
61 TryDeleteSilently(tempZip);
62 }
63
64 if (!Directory.Exists(knowledgeDir))
65 {
66 TryClearExtractFolder(extractRoot);
67 _resolvedCanonRoot = null;
68 return null;
69 }
70
71 File.WriteAllText(markerPath, hash + "\n", System.Text.Encoding.UTF8);
72 }
73
74 _resolvedCanonRoot = extractRoot;
75 return extractRoot;
76 }
77 }
78
79 private static void TryClearExtractFolder(string extractRoot)
80 {
81 try
82 {
83 if (!Directory.Exists(extractRoot))
84 return;
85 foreach (var f in Directory.EnumerateFiles(extractRoot))
86 TryDeleteSilently(f);
87 foreach (var sub in Directory.EnumerateDirectories(extractRoot))
88 Directory.Delete(sub, recursive: true);
89 }
90 catch
91 {
92 // повтор попытку на следующем заходе
93 }
94 }
95
96 private static void TryDeleteSilently(string path)
97 {
98 try
99 {
100 if (File.Exists(path))
101 File.Delete(path);
102 }
103 catch
104 {
105 // ignore
106 }
107 }
108}
109
View only · write via MCP/CIDE