Skip to content

Commit b5eee3b

Browse files
committed
Add packing mechanism on Indexer
Notes: Although the package is a Zip archive, the asset inside is not compressed with deflate, rather it's brotli compressed. The reason behind this is because Brotli is often produce more efficient compressed file rather than deflate. The zip here is just a packer for the files.
1 parent 621240d commit b5eee3b

File tree

3 files changed

+85
-6
lines changed

3 files changed

+85
-6
lines changed

Indexer/Indexer.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
<AssemblyName>Indexer</AssemblyName>
1515
<ProductName>Collapse Launcher Index Generator</ProductName>
1616
<Product>Collapse</Product>
17-
<Description>This library is a plugin build executable which generates a metadata contains the index of the plugin libraries.</Description>
17+
<Description>This library is a plugin build executable which generates a metadata and package for the plugin library.</Description>
1818
<AssemblyTitle>Collapse Launcher Index Generator</AssemblyTitle>
1919
<Company>Collapse Launcher Team</Company>
2020
<Authors>$(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm.</Authors>
2121
<Copyright>Copyright 2022-2025 $(Company)</Copyright>
22-
<Version>1.0.0</Version>
22+
<Version>1.1.0</Version>
2323
<Configurations>Debug;Release</Configurations>
2424
</PropertyGroup>
2525

Indexer/Program.cs

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@
77
using System.Collections.Generic;
88
using System.Diagnostics.CodeAnalysis;
99
using System.IO;
10+
using System.IO.Compression;
1011
using System.Linq;
1112
using System.Runtime.InteropServices;
1213
using System.Runtime.InteropServices.Marshalling;
1314
using System.Security.Cryptography;
15+
using System.Text;
1416
using System.Text.Encodings.Web;
1517
using System.Text.Json;
1618
using System.Threading;
1719
using System.Threading.Tasks;
20+
// ReSharper disable AccessToDisposedClosure
1821

1922
namespace Indexer;
2023

@@ -31,6 +34,7 @@ public class Program
3134
{
3235
private static readonly string[] AllowedPluginExt = [".dll", ".exe", ".so", ".dylib"];
3336
private static readonly SearchValues<string> AllowedPluginExtSearchValues = SearchValues.Create(AllowedPluginExt, StringComparison.OrdinalIgnoreCase);
37+
private static readonly string PackageExtension = ".zip";
3438

3539
public static int Main(params string[] args)
3640
{
@@ -57,7 +61,8 @@ public static int Main(params string[] args)
5761
}
5862

5963
string referenceFilePath = Path.Combine(path, "manifest.json");
60-
return WriteToJson(reference, referenceFilePath, assetInfo);
64+
int retCode = WriteToJson(reference, referenceFilePath, assetInfo);
65+
return retCode != 0 ? retCode : PackFiles(path, reference, assetInfo);
6166
}
6267
catch (Exception ex)
6368
{
@@ -96,7 +101,7 @@ private static int WriteToJson(PluginManifest reference, string referenceFilePat
96101
writer.WriteString(nameof(PluginManifest.PluginStandardVersion), reference.PluginStandardVersion.ToString());
97102
writer.WriteString(nameof(PluginManifest.PluginVersion), reference.PluginVersion.ToString());
98103
writer.WriteString(nameof(PluginManifest.PluginCreationDate), creationDate);
99-
writer.WriteString(nameof(PluginManifest.ManifestDate), DateTimeOffset.Now);
104+
writer.WriteString(nameof(PluginManifest.ManifestDate), reference.ManifestDate);
100105
if (reference.PluginAlternativeIcon?.Length != 0)
101106
{
102107
writer.WriteString(nameof(PluginManifest.PluginAlternativeIcon), reference.PluginAlternativeIcon);
@@ -122,6 +127,75 @@ private static int WriteToJson(PluginManifest reference, string referenceFilePat
122127
return 0;
123128
}
124129

130+
private static int PackFiles(string outputPath, PluginManifest referenceInfo, List<SelfUpdateAssetInfo> fileList)
131+
{
132+
string packageName = $"{Path.GetFileNameWithoutExtension(referenceInfo.MainLibraryName)}_{referenceInfo.PluginVersion}_API-{referenceInfo.PluginStandardVersion}_{referenceInfo.ManifestDate.ToString("yyyyMMdd")}";
133+
string packageFilePath = Path.Combine(outputPath, packageName + PackageExtension);
134+
135+
int threads = Environment.ProcessorCount;
136+
137+
Console.WriteLine($"Writing output package in parallel using {threads} threads at: {packageFilePath}...");
138+
139+
try
140+
{
141+
using FileStream packageFileStream = File.Create(packageFilePath);
142+
using ZipArchive packageWriter = new ZipArchive(packageFileStream, ZipArchiveMode.Create, false, Encoding.UTF8);
143+
144+
fileList.Add(new SelfUpdateAssetInfo
145+
{
146+
FileHash = [],
147+
FilePath = "manifest.json"
148+
});
149+
150+
int length = fileList.Count;
151+
int count = 0;
152+
153+
Lock thisLock = new Lock();
154+
Parallel.ForEach(fileList, CompressBrotliAndCreate);
155+
156+
return 0;
157+
158+
void CompressBrotliAndCreate(SelfUpdateAssetInfo asset)
159+
{
160+
Interlocked.Increment(ref count);
161+
int currentCount = count;
162+
163+
string filePath = Path.Combine(outputPath, asset.FilePath);
164+
DateTime lastWriteTime = File.GetLastWriteTimeUtc(filePath);
165+
166+
if (lastWriteTime.Year is < 1980 or > 2107)
167+
lastWriteTime = new DateTime(1980, 1, 1, 0, 0, 0);
168+
169+
Console.WriteLine($" [{currentCount}/{length}] Compressing asset to buffer: {asset.FilePath}");
170+
using MemoryStream compressedStream = new MemoryStream();
171+
using BrotliStream brotliStream = new BrotliStream(compressedStream, CompressionLevel.SmallestSize);
172+
using FileStream fileStream = File.OpenRead(filePath);
173+
174+
fileStream.CopyTo(brotliStream);
175+
brotliStream.Flush();
176+
177+
compressedStream.Position = 0;
178+
179+
using (thisLock.EnterScope())
180+
{
181+
Console.WriteLine($" [{currentCount}/{length}] Compress done. Now locking and writing buffer to package for: {asset.FilePath}");
182+
183+
string entryBrExt = asset.FilePath + ".br";
184+
ZipArchiveEntry entry = packageWriter.CreateEntry(entryBrExt, CompressionLevel.NoCompression);
185+
entry.LastWriteTime = lastWriteTime;
186+
187+
using Stream entryStream = entry.Open();
188+
compressedStream.CopyTo(entryStream);
189+
}
190+
}
191+
}
192+
catch (Exception e)
193+
{
194+
Console.WriteLine(e);
195+
return Marshal.GetHRForException(e);
196+
}
197+
}
198+
125199
private static FileInfo? FindPluginLibraryAndGetAssets(string dirPath, out List<SelfUpdateAssetInfo> fileList, out PluginManifest? referenceInfo)
126200
{
127201
DirectoryInfo directoryInfo = new DirectoryInfo(dirPath);
@@ -140,6 +214,10 @@ private static int WriteToJson(PluginManifest reference, string referenceFilePat
140214
void Impl(FileInfo fileInfo)
141215
{
142216
string fileName = fileInfo.FullName.AsSpan(directoryInfo.FullName.Length).TrimStart("\\/").ToString();
217+
if (fileName.EndsWith(PackageExtension, StringComparison.OrdinalIgnoreCase))
218+
{
219+
return;
220+
}
143221

144222
if (mainLibraryFileInfo == null &&
145223
IsPluginLibrary(fileInfo, fileName, out PluginManifest? referenceInfoInner))
@@ -265,7 +343,8 @@ private static unsafe bool IsPluginLibrary(FileInfo fileInfo, string fileName, [
265343
PluginVersion = pluginVersion,
266344
PluginStandardVersion = pluginStandardVersion,
267345
PluginAlternativeIcon = TryGetAlternateIconData(plugin),
268-
MainLibraryName = fileName
346+
MainLibraryName = fileName,
347+
ManifestDate = DateTimeOffset.UtcNow
269348
};
270349
return true;
271350
}

Indexer/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"Indexer": {
44
"commandName": "Project",
5-
"commandLineArgs": "H:\\myGit\\Hi3Helper.Plugin.HBR\\Hi3Helper.Plugin.HBR\\publish\\Debug"
5+
"commandLineArgs": "H:\\myGit\\Hi3Helper.Plugin.HBR\\Hi3Helper.Plugin.HBR\\publish\\Release"
66
}
77
}
88
}

0 commit comments

Comments
 (0)