Skip to content
This repository was archived by the owner on Mar 15, 2024. It is now read-only.

Commit 1972535

Browse files
authored
Merge pull request #16 from CnCNet/devo1929/copy-retry-tasks
copy retry tasks
2 parents e37476a + 38f0632 commit 1972535

File tree

2 files changed

+103
-39
lines changed

2 files changed

+103
-39
lines changed

ClientUpdater/Updater.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1284,7 +1284,8 @@ private static async Task PerformUpdateAsync()
12841284
using var _ = Process.Start(new ProcessStartInfo
12851285
{
12861286
FileName = "dotnet",
1287-
Arguments = "\"" + secondStageUpdaterResource.FullName + "\" " + CallingExecutableFileName + " \"" + GamePath + "\""
1287+
Arguments = "\"" + secondStageUpdaterResource.FullName + "\" " + CallingExecutableFileName + " \"" + GamePath + "\"",
1288+
UseShellExecute = true
12881289
});
12891290

12901291
Restart?.Invoke(null, EventArgs.Empty);

SecondStageUpdater/Program.cs

Lines changed: 101 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,19 @@ namespace SecondStageUpdater;
2323
using System.Reflection;
2424
using System.Runtime.InteropServices;
2525
using System.Threading;
26+
using System.Threading.Tasks;
2627
using Rampastring.Tools;
2728

2829
internal sealed class Program
2930
{
3031
private const int MutexTimeoutInSeconds = 30;
32+
private const int MaxCopyAttempts = 5;
33+
private const int CopyRetryWaitMilliseconds = 500;
3134

32-
private static ConsoleColor defaultColor;
33-
private static bool hasHandle;
34-
private static Mutex clientMutex;
35+
private static readonly object consoleMessageLock = new();
3536

36-
private static void Main(string[] args)
37+
private static async Task Main(string[] args)
3738
{
38-
defaultColor = Console.ForegroundColor;
39-
4039
try
4140
{
4241
Write("CnCNet Client Second-Stage Updater", ConsoleColor.Green);
@@ -61,7 +60,8 @@ private static void Main(string[] args)
6160

6261
string clientMutexId = FormattableString.Invariant($"Global{Guid.Parse("1CC9F8E7-9F69-4BBC-B045-E734204027A9")}");
6362

64-
clientMutex = new(false, clientMutexId, out _);
63+
Mutex clientMutex = new(false, clientMutexId, out _);
64+
bool hasHandle;
6565

6666
try
6767
{
@@ -78,6 +78,12 @@ private static void Main(string[] args)
7878
Exit(false);
7979
}
8080

81+
clientMutex.ReleaseMutex();
82+
clientMutex.Dispose();
83+
84+
// This is occasionally necessary to prevent DLLs from being locked at the time that this update is attempting to overwrite them
85+
await Task.Delay(1000).ConfigureAwait(false);
86+
8187
DirectoryInfo updaterDirectory = SafePath.GetDirectory(baseDirectory.FullName, "Updater");
8288

8389
if (!updaterDirectory.Exists)
@@ -95,6 +101,9 @@ private static void Main(string[] args)
95101

96102
Write($"{nameof(SecondStageUpdater)}: {relativeExecutableFile}");
97103

104+
var copyTasks = new List<Task>();
105+
var failedFiles = new List<FileInfo>();
106+
98107
foreach (FileInfo fileInfo in files)
99108
{
100109
FileInfo relativeFileInfo = SafePath.GetFile(fileInfo.FullName[updaterDirectory.FullName.Length..]);
@@ -116,22 +125,19 @@ private static void Main(string[] args)
116125
}
117126
else
118127
{
119-
try
120-
{
121-
FileInfo copiedFile = SafePath.GetFile(baseDirectory.FullName, relativeFileInfo.ToString());
122-
123-
Write($"Updating {relativeFileInfo}");
124-
fileInfo.CopyTo(copiedFile.FullName, true);
125-
}
126-
catch (Exception ex)
127-
{
128-
Write($"Updating file failed! Returned error message: {ex}", ConsoleColor.Yellow);
129-
Write("If the problem persists, try to move the content of the \"Updater\" directory to the main directory manually or contact the staff for support.");
130-
Exit(false);
131-
}
128+
copyTasks.Add(CopyFileTaskAsync(baseDirectory, fileInfo, relativeFileInfo, failedFiles));
132129
}
133130
}
134131

132+
await Task.WhenAll(copyTasks.ToArray()).ConfigureAwait(false);
133+
134+
if (failedFiles.Any())
135+
{
136+
Write("Updating file(s) failed!", ConsoleColor.Yellow);
137+
Write("If the problem persists, try to move the content of the \"Updater\" directory to the main directory manually or contact the staff for support.");
138+
Exit(false);
139+
}
140+
135141
FileInfo versionFile = SafePath.GetFile(updaterDirectory.FullName, versionFileName);
136142

137143
if (versionFile.Exists)
@@ -150,7 +156,7 @@ private static void Main(string[] args)
150156
{
151157
Write("Checking ClientDefinitions.ini for launcher executable filename.");
152158

153-
string[] lines = File.ReadAllLines(SafePath.CombineFilePath(resourceDirectory.FullName, "ClientDefinitions.ini"));
159+
string[] lines = await File.ReadAllLinesAsync(SafePath.CombineFilePath(resourceDirectory.FullName, "ClientDefinitions.ini")).ConfigureAwait(false);
154160
string launcherPropertyName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "LauncherExe" : "UnixLauncherExe";
155161
string line = lines.Single(q => q.Trim().StartsWith(launcherPropertyName, StringComparison.OrdinalIgnoreCase) && q.Contains('=', StringComparison.OrdinalIgnoreCase));
156162
int commentStart = line.IndexOf(';', StringComparison.OrdinalIgnoreCase);
@@ -197,32 +203,89 @@ private static void Main(string[] args)
197203
}
198204
}
199205

200-
private static void Exit(bool success)
206+
/// <summary>
207+
/// This attempts to copy a file for the update with the ability to retry up to <see cref="MaxCopyAttempts"/> times.
208+
/// There are instances where DLLs or other files may be locked and are unable to be overwritten by the update.
209+
///
210+
/// TODO:
211+
/// Make a backup of all files that are attempted. When we check for any failed files outside this function, restore all backups
212+
/// if any failures occurred. This will prevent the user from being in a partially updated state.
213+
///
214+
/// </summary>
215+
/// <param name="baseDirectory">The absolute path of the game installation.</param>
216+
/// <param name="sourceFileInfo">The file to be copied.</param>
217+
/// <param name="relativeFileInfo">The relative file info for the destination of the file to be copied.</param>
218+
/// <param name="failedFiles">If the copy fails too many times, the file should be added to this list.</param>
219+
/// <returns>A Task.</returns>
220+
private static async Task CopyFileTaskAsync(DirectoryInfo baseDirectory, FileInfo sourceFileInfo, FileInfo relativeFileInfo, List<FileInfo> failedFiles)
201221
{
202-
if (hasHandle)
222+
for (int attempt = 1; ; attempt++)
203223
{
204-
clientMutex.ReleaseMutex();
205-
clientMutex.Dispose();
206-
}
224+
try
225+
{
226+
FileInfo destinationFile = SafePath.GetFile(baseDirectory.FullName, relativeFileInfo.ToString());
227+
FileStream sourceFileStream = sourceFileInfo.Open(new FileStreamOptions
228+
{
229+
Access = FileAccess.Read,
230+
Mode = FileMode.Open,
231+
Options = FileOptions.Asynchronous,
232+
Share = FileShare.None
233+
});
234+
await using (sourceFileStream.ConfigureAwait(false))
235+
{
236+
FileStream destinationFileStream = destinationFile.Open(new FileStreamOptions
237+
{
238+
Access = FileAccess.Write,
239+
Mode = FileMode.Create,
240+
Options = FileOptions.Asynchronous,
241+
Share = FileShare.None
242+
});
243+
await using (destinationFileStream.ConfigureAwait(false))
244+
{
245+
await sourceFileStream.CopyToAsync(destinationFileStream).ConfigureAwait(false);
246+
}
247+
}
207248

208-
if (!success)
209-
{
210-
Write("Press any key to exit.");
211-
Console.ReadKey();
212-
Environment.Exit(1);
249+
Write($"Updated {relativeFileInfo}");
250+
251+
// File was succesfully copied. Return from the function.
252+
return;
253+
}
254+
catch (IOException ex)
255+
{
256+
if (attempt >= MaxCopyAttempts)
257+
{
258+
// We tried too many times and need to bail.
259+
failedFiles.Add(sourceFileInfo);
260+
Write($"Updating file failed too many times! Returned error message: {ex}", ConsoleColor.Yellow);
261+
return;
262+
}
263+
264+
// We failed to copy the file, but can try again.
265+
Write($"Updating file attempt {attempt} failed! Returned error message: {ex.Message}", ConsoleColor.Yellow);
266+
await Task.Delay(CopyRetryWaitMilliseconds).ConfigureAwait(false);
267+
}
213268
}
214269
}
215270

216-
private static void Write(string text)
271+
private static void Exit(bool success)
217272
{
218-
Console.ForegroundColor = defaultColor;
219-
Console.WriteLine(text);
273+
if (success)
274+
return;
275+
276+
Write("Press any key to exit.");
277+
Console.ReadKey();
278+
Environment.Exit(1);
220279
}
221280

222-
private static void Write(string text, ConsoleColor color)
281+
private static void Write(string text, ConsoleColor? color = null)
223282
{
224-
Console.ForegroundColor = color;
225-
Console.WriteLine(text);
226-
Console.ForegroundColor = defaultColor;
283+
// This is necessary, because console is written to from the copy file task
284+
lock (consoleMessageLock)
285+
{
286+
Console.ForegroundColor = color ?? Console.ForegroundColor;
287+
Console.WriteLine(text);
288+
Console.ResetColor();
289+
}
227290
}
228291
}

0 commit comments

Comments
 (0)