Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -192,37 +192,26 @@
await new DefaultDownloader().DownloadManyAsync(groupRequest, cancellationToken);
}

private async Task ExtractModpackAsync(CancellationToken cancellationToken) {

Check notice on line 195 in MinecraftLaunch/Components/Installer/Modpack/CurseforgeModpackInstaller.cs

View check run for this annotation

codefactor.io / CodeFactor

MinecraftLaunch/Components/Installer/Modpack/CurseforgeModpackInstaller.cs#L195

An opening brace should not be followed by a blank line. (SA1505)
var zipArchive = ZipFile.OpenRead(ModpackPath);
Comment thread
IksRain marked this conversation as resolved.
var entries = zipArchive?.Entries;
ReportProgress(InstallStep.ExtractModpack, 0.85d, TaskStatus.Running, entries.Count, 0);

int count = 0;
var tasks = entries.Select(x => Task.Run(() => {
lock (zipArchive) {
ReportProgress(InstallStep.ExtractModpack,
((double)Interlocked.Increment(ref count) / (double)entries.Count).ToPercentage(0.85d, 1.0d),
TaskStatus.Running, entries.Count, count);

if (!Entry.IsOverride ||
!x.FullName.StartsWith(Entry.Overrides, StringComparison.OrdinalIgnoreCase)) return;

var subPath = x.FullName[(Entry.Overrides.Length + 1)..];
if (string.IsNullOrEmpty(subPath))
return;

var filePath = new FileInfo(Path.Combine(Path.GetFullPath(Minecraft.ToWorkingPath(true)), subPath));
if (x.FullName.EndsWith('/')) {
Directory.CreateDirectory(filePath.FullName);
return;
}

x.ExtractTo(filePath.FullName);
}
}, cancellationToken));

await Task.WhenAll(tasks);
zipArchive.Dispose();

ReportProgress(InstallStep.ExtractModpack, 0.85d, TaskStatus.Running, 0, 0); // 此处未开始解析,返回0

var count = 0;
await ModPackUtils.ExtractSingleThreadAsync(
srcZipPath: ModpackPath,
overridesPrefix: Entry.Overrides,
independentAndFullWorkingPath: Minecraft.ToWorkingPath(true),
whenEachEntryCompleted: ReportEntryExtractingProgress,
cancellationToken: cancellationToken);
return;

void ReportEntryExtractingProgress(ZipArchive zipArchive) =>
ReportProgress(
step: InstallStep.ExtractModpack,
progress: (Interlocked.Increment(ref count) / (double)zipArchive.Entries.Count).ToPercentage(0.85d, 1.0d),
status: TaskStatus.Running,
totalCount: zipArchive.Entries.Count,
finshedCount: count);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,40 +74,29 @@

#region Privates

private async Task ExtractModpackAsync(CancellationToken cancellationToken) {

Check notice on line 77 in MinecraftLaunch/Components/Installer/Modpack/McbbsModpackInstaller.cs

View check run for this annotation

codefactor.io / CodeFactor

MinecraftLaunch/Components/Installer/Modpack/McbbsModpackInstaller.cs#L77

An opening brace should not be followed by a blank line. (SA1505)
var zipArchive = ZipFile.OpenRead(ModpackPath);
Comment thread
IksRain marked this conversation as resolved.
var entries = zipArchive?.Entries;
ReportProgress(InstallStep.ExtractModpack, 0.10d, TaskStatus.Running, entries.Count, 0);

ReportProgress(InstallStep.ExtractModpack, 0.85d, TaskStatus.Running, 0, 0); // 此处未开始解析,返回0

const string decompressPrefix = "overrides";
string woringPath = Minecraft.ToWorkingPath(true);

int count = 0;
var tasks = entries.Select(x => Task.Run(() => {
lock (zipArchive) {
ReportProgress(InstallStep.ExtractModpack,
((double)Interlocked.Increment(ref count) / (double)entries.Count).ToPercentage(0.1d, 1.0d),
TaskStatus.Running, entries.Count, count);

if (!x.FullName.StartsWith(decompressPrefix))
return;

var subPath = x.FullName[(decompressPrefix.Length + 1)..];
if (string.IsNullOrEmpty(subPath))
return;

var filePath = new FileInfo(Path.Combine(woringPath, subPath));
if (x.FullName.EndsWith('/')) {
filePath.Directory.Create();
return;
}

x.ExtractTo(filePath.FullName);
}
}, cancellationToken));

await Task.WhenAll(tasks);
zipArchive.Dispose();

var count = 0;
await ModPackUtils.ExtractSingleThreadAsync(
srcZipPath: ModpackPath,
overridesPrefix: decompressPrefix,
independentAndFullWorkingPath: Minecraft.ToWorkingPath(true),
whenEachEntryCompleted: ReportEntryExtractingProgress,
cancellationToken: cancellationToken);
return;

Check notice on line 91 in MinecraftLaunch/Components/Installer/Modpack/McbbsModpackInstaller.cs

View check run for this annotation

codefactor.io / CodeFactor

MinecraftLaunch/Components/Installer/Modpack/McbbsModpackInstaller.cs#L91

Code should not contain multiple blank lines in a row. (SA1507)
Comment thread
IksRain marked this conversation as resolved.

void ReportEntryExtractingProgress(ZipArchive zipArchive) =>
ReportProgress(
step: InstallStep.ExtractModpack,
progress: (Interlocked.Increment(ref count) / (double)zipArchive.Entries.Count).ToPercentage(0.85d, 1.0d),
status: TaskStatus.Running,
totalCount: zipArchive.Entries.Count,
finshedCount: count);
}

#endregion
Expand Down
74 changes: 74 additions & 0 deletions MinecraftLaunch/Components/Installer/Modpack/ModPackUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO.Compression;
using System.Net.Sockets;
using MinecraftLaunch.Extensions;

namespace MinecraftLaunch.Components.Installer.Modpack;

internal static class ModPackUtils
{
public const char ZipPathSeparator = '/';

Check notice on line 11 in MinecraftLaunch/Components/Installer/Modpack/ModPackUtils.cs

View check run for this annotation

codefactor.io / CodeFactor

MinecraftLaunch/Components/Installer/Modpack/ModPackUtils.cs#L11

Code should not contain multiple blank lines in a row. (SA1507)

Comment thread
IksRain marked this conversation as resolved.

public static async Task ExtractSingleThreadAsync(
string srcZipPath,
string overridesPrefix,
string independentAndFullWorkingPath,
/*执行线程不保证*/Action<ZipArchive> whenEachEntryCompleted = null,
CancellationToken cancellationToken = default)
{
Debug.Assert(srcZipPath is not null || overridesPrefix is not null || independentAndFullWorkingPath is not null);
cancellationToken.ThrowIfCancellationRequested();
using var zip = ZipFile.OpenRead(srcZipPath);
foreach (var item in zip.Entries)
{
cancellationToken.ThrowIfCancellationRequested();
// 排除非 <overrides>/文件
if (!item.FullName.StartsWith(overridesPrefix, StringComparison.OrdinalIgnoreCase)) continue;
var targetPath = Path.Combine(
independentAndFullWorkingPath,
RemoveOverridesPrefix(item.FullName, overridesPrefix));
if (item.FullName.EndsWith(ZipPathSeparator))
{
Directory.CreateDirectory(targetPath);
continue;
}
if (!IsShouldExtract(item, overridesPrefix)) continue;
await item.ExtractToFileAsync(
targetPath,
overwrite:true,cancellationToken
).ConfigureAwait(false);
whenEachEntryCompleted?.Invoke(zip);
}
}

#region Util

private static bool IsShouldExtract(
ZipArchiveEntry entry,
string overridesPrefix)
{
// 排除目录
if (entry.FullName.EndsWith(ZipPathSeparator)) return false;
// 排除非 <overrides>/文件
if (!entry.FullName.StartsWith(overridesPrefix, StringComparison.OrdinalIgnoreCase)) return false;
return true;
}

// 修正路径
private static string RemoveOverridesPrefix(
string source,
string overridesPrefix)
{
if (overridesPrefix.EndsWith('/'))
{
return source[overridesPrefix.Length..];
}

// 补充/
return source[(overridesPrefix.Length + 1)..];
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@
}

public static async Task<IInstallEntry> ParseModLoaderEntryAsync(ModrinthModpackInstallEntry modpack, CancellationToken cancellationToken = default) {
if (modpack.Dependencies.ContainsKey("fabric-loader"))
if (modpack.Dependencies.TryGetValue("fabric-loader", out var modpackDependency1))
return (await FabricInstaller.EnumerableFabricAsync(modpack.McVersion, cancellationToken: cancellationToken))
.First(x => x.BuildVersion.Equals(modpack.Dependencies["fabric-loader"]));
else if (modpack.Dependencies.ContainsKey("quilt-loader"))
.First(x => x.BuildVersion.Equals(modpackDependency1));
else if (modpack.Dependencies.TryGetValue("quilt-loader", out var dependency1))
return (await QuiltInstaller.EnumerableQuiltAsync(modpack.McVersion, cancellationToken))
.First(x => x.BuildVersion.Equals(modpack.Dependencies["quilt-loader"]));
else if (modpack.Dependencies.ContainsKey("forge"))
.First(x => x.BuildVersion.Equals(dependency1));
else if (modpack.Dependencies.TryGetValue("forge", out var modpackDependency))
return (await ForgeInstaller.EnumerableForgeAsync(modpack.McVersion, false, cancellationToken))
.First(x => x.ForgeVersion.Equals(modpack.Dependencies["forge"]));
else if (modpack.Dependencies.ContainsKey("neoforge"))
.First(x => x.ForgeVersion.Equals(modpackDependency));
else if (modpack.Dependencies.TryGetValue("neoforge", out var dependency))
return (await ForgeInstaller.EnumerableForgeAsync(modpack.McVersion, true, cancellationToken))
.First(x => x.ForgeVersion.Equals(modpack.Dependencies["neoforge"]));
.First(x => x.ForgeVersion.Equals(dependency));
else
throw new NotSupportedException();
}
Expand Down Expand Up @@ -70,30 +70,37 @@

#region Privates

private IEnumerable<DownloadRequest> ParseModFiles(CancellationToken cancellationToken) {
int totalCount = Entry.Files.Count();
ReportProgress(InstallStep.ParseDownloadUrls, 0.1d, TaskStatus.Running, totalCount, 0);

int count = 0;
string versionPath = Minecraft.ToWorkingPath(true);
foreach (var file in Entry.Files.AsParallel()) {
private IEnumerable<DownloadRequest> ParseModFiles(CancellationToken cancellationToken)
{
const double minProgress = 0.1d;
const double maxProgress = 0.45d;
var fileArray = Entry.Files.ToArray();
var constTotalCount = fileArray.Length;
ReportProgress(
step: InstallStep.ParseDownloadUrls,
progress: 0.1d,
status: TaskStatus.Running,
totalCount: constTotalCount,
finshedCount: 0);
double count = 0;
var versionPath = Minecraft.ToWorkingPath(true);
//不对Parallel进行Foreach,直接不Parallel
return fileArray.Select(fileItem =>
{
cancellationToken.ThrowIfCancellationRequested();

lock (Entry) {
double progress = (double)Interlocked.Increment(ref count) / (double)totalCount;
ReportProgress(InstallStep.ParseDownloadUrls, progress.ToPercentage(0.1d, 0.45d),
TaskStatus.Running, totalCount, count);
}

if (!file.Downloads.Any())
continue;

if (string.IsNullOrEmpty(file.Path))
continue;

var filePath = Path.Combine(versionPath, file.Path);
yield return new DownloadRequest(file.Downloads.First(), filePath);
}
// 非多线程且同个闭包可见性好,无需原子操作
ReportProgress(
step: InstallStep.ParseDownloadUrls,
// ReSharper disable once AccessToModifiedClosure
progress: (++count / constTotalCount).ToPercentage(minProgress, maxProgress),
status: TaskStatus.Running,
totalCount: constTotalCount,
finshedCount: 0);
if (!fileItem.Downloads.Any()) return null;
if (string.IsNullOrEmpty(fileItem.Path)) return null;
var filePath = Path.Combine(versionPath, fileItem.Path);
return new DownloadRequest(fileItem.Downloads.First(), filePath);
}).Where(static x => x is not null);
}

private Task<GroupDownloadResult> DownloadModsAsync(IEnumerable<DownloadRequest> downloadRequests, CancellationToken cancellationToken) {
Expand All @@ -110,40 +117,29 @@
.DownloadManyAsync(groupRequest, cancellationToken);
}

private async Task ExtractModpackAsync(CancellationToken cancellationToken) {

Check notice on line 120 in MinecraftLaunch/Components/Installer/Modpack/ModrinthModpackInstaller.cs

View check run for this annotation

codefactor.io / CodeFactor

MinecraftLaunch/Components/Installer/Modpack/ModrinthModpackInstaller.cs#L120

An opening brace should not be followed by a blank line. (SA1505)
var zipArchive = ZipFile.OpenRead(ModpackPath);
Comment thread
IksRain marked this conversation as resolved.
var entries = zipArchive?.Entries;
ReportProgress(InstallStep.ExtractModpack, 0.85d, TaskStatus.Running, entries.Count, 0);

ReportProgress(InstallStep.ExtractModpack, 0.85d, TaskStatus.Running, 0, 0); // 此处未开始解析,返回0

const string decompressPrefix = "overrides";
string woringPath = Minecraft.ToWorkingPath(true);

int count = 0;
var tasks = entries.Select(x => Task.Run(() => {
lock (zipArchive) {
ReportProgress(InstallStep.ExtractModpack,
((double)Interlocked.Increment(ref count) / (double)entries.Count).ToPercentage(0.85d, 1.0d),
TaskStatus.Running, entries.Count, count);

if (!x.FullName.StartsWith(decompressPrefix))
return;

var subPath = x.FullName[(decompressPrefix.Length + 1)..];
if (string.IsNullOrEmpty(subPath))
return;

var filePath = new FileInfo(Path.Combine(woringPath, subPath));
if (x.FullName.EndsWith('/')) {
filePath.Directory.Create();
return;
}

x.ExtractTo(filePath.FullName);
}
}, cancellationToken));

await Task.WhenAll(tasks);
zipArchive.Dispose();

var count = 0;
await ModPackUtils.ExtractSingleThreadAsync(
srcZipPath: ModpackPath,
overridesPrefix: decompressPrefix,
independentAndFullWorkingPath: Minecraft.ToWorkingPath(true),
whenEachEntryCompleted: ReportEntryExtractingProgress,
cancellationToken: cancellationToken);
return;

Check notice on line 134 in MinecraftLaunch/Components/Installer/Modpack/ModrinthModpackInstaller.cs

View check run for this annotation

codefactor.io / CodeFactor

MinecraftLaunch/Components/Installer/Modpack/ModrinthModpackInstaller.cs#L134

Code should not contain multiple blank lines in a row. (SA1507)
Comment thread
IksRain marked this conversation as resolved.

void ReportEntryExtractingProgress(ZipArchive zipArchive) =>
ReportProgress(
step: InstallStep.ExtractModpack,
progress: (Interlocked.Increment(ref count) / (double)zipArchive.Entries.Count).ToPercentage(0.85d, 1.0d),
status: TaskStatus.Running,
totalCount: zipArchive.Entries.Count,
finshedCount: count);
}

#endregion
Expand Down
44 changes: 44 additions & 0 deletions MinecraftLaunch/Extensions/ZipArchiveExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,48 @@ public static void ExtractTo(this ZipArchiveEntry zipArchiveEntry, string destin

zipArchiveEntry.ExtractToFile(destinationFile, true);
}

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// -> The .NET Foundation licenses this function to us under the MIT license.
// 使用了.net标准库代码
private static void ExtractToFileInitialize(ZipArchiveEntry source, string destinationFileName, bool overwrite, out FileStreamOptions fileStreamOptions)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(destinationFileName);

fileStreamOptions = new()
{
Access = FileAccess.Write,
Mode = overwrite ? FileMode.Create : FileMode.CreateNew,
Share = FileShare.None,
BufferSize = 16384
};

const UnixFileMode OwnershipPermissions =
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;

// Restore Unix permissions.
// For security, limit to ownership permissions, and respect umask (through UnixCreateMode).
// We don't apply UnixFileMode.None because .zip files created on Windows and .zip files created
// with previous versions of .NET don't include permissions.
UnixFileMode mode = (UnixFileMode)(source.ExternalAttributes >> 16) & OwnershipPermissions;
if (mode != UnixFileMode.None && !OperatingSystem.IsWindows())
{
fileStreamOptions.UnixCreateMode = mode;
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
internal static async Task ExtractToFileAsync(this ZipArchiveEntry zipArchiveEntry, string destinationFile,bool overwrite,CancellationToken cts)
{
cts.ThrowIfCancellationRequested();
ExtractToFileInitialize(zipArchiveEntry, destinationFile, overwrite, out var fileStreamOptions);
await using var dst = new FileStream(destinationFile, fileStreamOptions);
await using var src = zipArchiveEntry.Open(); // OpenAsync真搬不了吧(),等xilu速速换NET10单目标即可
await src.CopyToAsync(dst, cts).ConfigureAwait(false);
File.SetLastWriteTime(destinationFile,DateTime.Now);
}
}