Skip to content

Commit ac00d94

Browse files
committed
Handle file writing errors for movies and all tools.
1 parent d4e56fe commit ac00d94

File tree

14 files changed

+283
-156
lines changed

14 files changed

+283
-156
lines changed

src/BizHawk.Client.Common/DialogControllerExtensions.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66

77
namespace BizHawk.Client.Common
88
{
9+
public enum TryAgainResult
10+
{
11+
Saved,
12+
IgnoredFailure,
13+
Canceled,
14+
}
15+
916
public static class DialogControllerExtensions
1017
{
1118
public static void AddOnScreenMessage(
@@ -84,7 +91,7 @@ public static void ErrorMessageBox(
8491
/// The user will be repeatedly asked if they want to try again until either success or the user says no.
8592
/// </summary>
8693
/// <returns>Returns true on success or if the user said no. Returns false if the user said cancel.</returns>
87-
public static bool DoWithTryAgainBox(
94+
public static TryAgainResult DoWithTryAgainBox(
8895
this IDialogParent dialogParent,
8996
Func<FileWriteResult> action,
9097
string message)
@@ -98,12 +105,12 @@ public static bool DoWithTryAgainBox(
98105
$"{fileResult.UserFriendlyErrorMessage()}\n{fileResult.Exception!.Message}",
99106
caption: "Error",
100107
icon: EMsgBoxIcon.Error);
101-
if (askResult == null) return false;
102-
if (askResult == false) return true;
108+
if (askResult == null) return TryAgainResult.Canceled;
109+
if (askResult == false) return TryAgainResult.IgnoredFailure;
103110
if (askResult == true) fileResult = action();
104111
}
105112

106-
return true;
113+
return TryAgainResult.Saved;
107114
}
108115

109116
/// <summary>Creates and shows a <c>System.Windows.Forms.OpenFileDialog</c> or equivalent with the receiver (<paramref name="dialogParent"/>) as its parent</summary>

src/BizHawk.Client.Common/FileWriter.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ public static FileWriteResult Write(string path, byte[] bytes, string? backupPat
5656
return createResult.Value.CloseAndDispose(backupPath);
5757
}
5858

59+
public static FileWriteResult Write(string path, Action<Stream> writeCallback, string? backupPath = null)
60+
{
61+
FileWriteResult<FileWriter> createResult = Create(path);
62+
if (createResult.IsError) return createResult;
63+
64+
try
65+
{
66+
writeCallback(createResult.Value!.Stream);
67+
}
68+
catch (Exception ex)
69+
{
70+
return new(FileWriteEnum.FailedDuringWrite, createResult.Value!.Paths, ex);
71+
}
72+
73+
return createResult.Value.CloseAndDispose(backupPath);
74+
}
5975

6076
/// <summary>
6177
/// Create a FileWriter instance, or return an error if unable to access the file.
@@ -76,6 +92,7 @@ public static FileWriteResult<FileWriter> Create(string path)
7692
FileWritePaths paths = new(path, writePath);
7793
try
7894
{
95+
Directory.CreateDirectory(Path.GetDirectoryName(path));
7996
FileStream fs = new(writePath, FileMode.Create, FileAccess.Write);
8097
return new(new FileWriter(paths, fs), paths);
8198
}

src/BizHawk.Client.Common/lua/LuaFileList.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,8 @@ public bool Load(string path, bool disableOnLoad)
102102
return true;
103103
}
104104

105-
public void Save(string path)
105+
public FileWriteResult Save(string path)
106106
{
107-
using var sw = new StreamWriter(path);
108107
var sb = new StringBuilder();
109108
var saveDirectory = Path.GetDirectoryName(Path.GetFullPath(path));
110109
foreach (var file in this)
@@ -123,10 +122,19 @@ public void Save(string path)
123122
}
124123
}
125124

126-
sw.Write(sb.ToString());
125+
FileWriteResult result = FileWriter.Write(path, (fs) =>
126+
{
127+
using var sw = new StreamWriter(fs);
128+
sw.Write(sb.ToString());
129+
});
127130

128-
Filename = path;
129-
Changes = false;
131+
if (!result.IsError)
132+
{
133+
Filename = path;
134+
Changes = false;
135+
}
136+
137+
return result;
130138
}
131139
}
132140
}

src/BizHawk.Client.Common/movie/MovieSession.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,8 @@ private void HandlePlaybackEnd()
385385
switch (Settings.MovieEndAction)
386386
{
387387
case MovieEndAction.Stop:
388+
// Technically this can save the movie, but it'd be weird to be in that situation.
389+
// Do we want that?
388390
StopMovie();
389391
break;
390392
case MovieEndAction.Record:

src/BizHawk.Client.Common/tools/CheatList.cs

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ public void DisableAll()
210210
public bool IsActive(MemoryDomain domain, long address)
211211
=> _cheatList.Exists(cheat => !cheat.IsSeparator && cheat.Enabled && cheat.Domain == domain && cheat.Contains(address));
212212

213-
public void SaveOnClose()
213+
public FileWriteResult SaveOnClose()
214214
{
215215
if (_config.AutoSaveOnClose)
216216
{
@@ -221,17 +221,27 @@ public void SaveOnClose()
221221
CurrentFileName = _defaultFileName;
222222
}
223223

224-
SaveFile(CurrentFileName);
224+
return SaveFile(CurrentFileName);
225225
}
226226
else if (_cheatList.Count is 0 && !string.IsNullOrWhiteSpace(CurrentFileName))
227227
{
228-
File.Delete(CurrentFileName);
228+
try
229+
{
230+
File.Delete(CurrentFileName);
231+
}
232+
catch (Exception ex)
233+
{
234+
return new(FileWriteEnum.FailedToDeleteGeneric, new(CurrentFileName, ""), ex);
235+
}
229236
_config.Recent.Remove(CurrentFileName);
237+
return new();
230238
}
231239
}
240+
241+
return new();
232242
}
233243

234-
public bool Save()
244+
public FileWriteResult Save()
235245
{
236246
if (string.IsNullOrWhiteSpace(CurrentFileName))
237247
{
@@ -241,54 +251,51 @@ public bool Save()
241251
return SaveFile(CurrentFileName);
242252
}
243253

244-
public bool SaveFile(string path)
254+
public FileWriteResult SaveFile(string path)
245255
{
246-
try
247-
{
248-
new FileInfo(path).Directory?.Create();
249-
var sb = new StringBuilder();
256+
var sb = new StringBuilder();
250257

251-
foreach (var cheat in _cheatList)
258+
foreach (var cheat in _cheatList)
259+
{
260+
if (cheat.IsSeparator)
252261
{
253-
if (cheat.IsSeparator)
254-
{
255-
sb.AppendLine("----");
256-
}
257-
else
258-
{
259-
// Set to hex for saving
260-
var tempCheatType = cheat.Type;
261-
262-
cheat.SetType(WatchDisplayType.Hex);
263-
264-
sb
265-
.Append(cheat.AddressStr).Append('\t')
266-
.Append(cheat.ValueStr).Append('\t')
267-
.Append(cheat.Compare is null ? "N" : cheat.CompareStr).Append('\t')
268-
.Append(cheat.Domain != null ? cheat.Domain.Name : "").Append('\t')
269-
.Append(cheat.Enabled ? '1' : '0').Append('\t')
270-
.Append(cheat.Name).Append('\t')
271-
.Append(cheat.SizeAsChar).Append('\t')
272-
.Append(cheat.TypeAsChar).Append('\t')
273-
.Append(cheat.BigEndian is true ? '1' : '0').Append('\t')
274-
.Append(cheat.ComparisonType).Append('\t')
275-
.AppendLine();
276-
277-
cheat.SetType(tempCheatType);
278-
}
262+
sb.AppendLine("----");
279263
}
280-
281-
File.WriteAllText(path, sb.ToString());
282-
264+
else
265+
{
266+
// Set to hex for saving
267+
var tempCheatType = cheat.Type;
268+
269+
cheat.SetType(WatchDisplayType.Hex);
270+
271+
sb
272+
.Append(cheat.AddressStr).Append('\t')
273+
.Append(cheat.ValueStr).Append('\t')
274+
.Append(cheat.Compare is null ? "N" : cheat.CompareStr).Append('\t')
275+
.Append(cheat.Domain != null ? cheat.Domain.Name : "").Append('\t')
276+
.Append(cheat.Enabled ? '1' : '0').Append('\t')
277+
.Append(cheat.Name).Append('\t')
278+
.Append(cheat.SizeAsChar).Append('\t')
279+
.Append(cheat.TypeAsChar).Append('\t')
280+
.Append(cheat.BigEndian is true ? '1' : '0').Append('\t')
281+
.Append(cheat.ComparisonType).Append('\t')
282+
.AppendLine();
283+
284+
cheat.SetType(tempCheatType);
285+
}
286+
}
287+
FileWriteResult result = FileWriter.Write(path, (fs) =>
288+
{
289+
StreamWriter sw = new(fs);
290+
sw.Write(sb.ToString());
291+
});
292+
if (!result.IsError)
293+
{
283294
CurrentFileName = path;
284295
_config.Recent.Add(CurrentFileName);
285296
Changes = false;
286-
return true;
287-
}
288-
catch
289-
{
290-
return false;
291297
}
298+
return result;
292299
}
293300

294301
public bool Load(IMemoryDomains domains, string path, bool append)

src/BizHawk.Client.Common/tools/Watch/WatchList/WatchList.cs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Globalization;
45
using System.IO;
56
using System.Linq;
@@ -338,39 +339,37 @@ public void Reload()
338339
}
339340
}
340341

341-
public bool Save()
342+
public FileWriteResult Save()
342343
{
343344
if (string.IsNullOrWhiteSpace(CurrentFileName))
344345
{
345-
return false;
346+
return new();
346347
}
347348

348-
using (var sw = new StreamWriter(CurrentFileName))
349-
{
350-
var sb = new StringBuilder();
351-
sb.Append("SystemID ").AppendLine(_systemId);
349+
var sb = new StringBuilder();
350+
sb.Append("SystemID ").AppendLine(_systemId);
352351

353-
foreach (var watch in _watchList)
354-
{
355-
sb.AppendLine(watch.ToString());
356-
}
352+
foreach (var watch in _watchList)
353+
{
354+
sb.AppendLine(watch.ToString());
355+
}
357356

357+
FileWriteResult result = FileWriter.Write(CurrentFileName, (fs) =>
358+
{
359+
using var sw = new StreamWriter(fs);
358360
sw.WriteLine(sb.ToString());
359-
}
361+
});
360362

361-
Changes = false;
362-
return true;
363+
if (!result.IsError) Changes = false;
364+
return result;
363365
}
364366

365-
public bool SaveAs(FileInfo file)
367+
public FileWriteResult SaveAs(FileInfo file)
366368
{
367-
if (file != null)
368-
{
369-
CurrentFileName = file.FullName;
370-
return Save();
371-
}
369+
Debug.Assert(file != null, "Cannot save as without a file name.");
372370

373-
return false;
371+
CurrentFileName = file.FullName;
372+
return Save();
374373
}
375374

376375
private bool LoadFile(string path, bool append)

src/BizHawk.Client.EmuHawk/MainForm.cs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3886,26 +3886,25 @@ private bool CloseGame(bool clearSram = false)
38863886
return false;
38873887
}
38883888
// There is a cheats tool, but cheats can be active while the "cheats tool" is not. And have auto-save option.
3889-
CheatList.SaveOnClose();
3889+
TryAgainResult cheatSaveResult = this.DoWithTryAgainBox(CheatList.SaveOnClose, "Failed to save cheats.");
3890+
if (cheatSaveResult == TryAgainResult.Canceled)
3891+
{
3892+
return false;
3893+
}
38903894

3891-
FileWriteResult saveResult = MovieSession.StopMovie();
3892-
if (saveResult.IsError)
3895+
// If TAStudio is open, we already asked about saving the movie.
3896+
if (!Tools.IsLoaded<TAStudio>())
38933897
{
3894-
if (!this.ModalMessageBox2(
3895-
caption: "Quit anyway?",
3896-
icon: EMsgBoxIcon.Question,
3897-
text: "The currently playing movie could not be saved. Continue and quit anyway? All unsaved changes will be lost."))
3898-
{
3899-
return false;
3900-
}
3898+
TryAgainResult saveMovieResult = this.DoWithTryAgainBox(() => MovieSession.StopMovie(), "Failed to save movie.");
3899+
if (saveMovieResult == TryAgainResult.Canceled) return false;
39013900
}
39023901

39033902
if (clearSram)
39043903
{
39053904
var path = Config.PathEntries.SaveRamAbsolutePath(Game, MovieSession.Movie);
39063905
if (File.Exists(path))
39073906
{
3908-
bool clearResult = this.DoWithTryAgainBox(() => {
3907+
TryAgainResult clearResult = this.DoWithTryAgainBox(() => {
39093908
try
39103909
{
39113910
File.Delete(path);
@@ -3917,22 +3916,22 @@ private bool CloseGame(bool clearSram = false)
39173916
return new(FileWriteEnum.FailedToDeleteGeneric, new(path, ""), ex);
39183917
}
39193918
}, "Failed to clear SRAM.");
3920-
if (!clearResult)
3919+
if (clearResult == TryAgainResult.Canceled)
39213920
{
39223921
return false;
39233922
}
39243923
}
39253924
}
39263925
else if (Emulator.HasSaveRam())
39273926
{
3928-
bool flushResult = this.DoWithTryAgainBox(
3927+
TryAgainResult flushResult = this.DoWithTryAgainBox(
39293928
() => FlushSaveRAM(),
39303929
"Failed flushing the game's Save RAM to your disk.");
3931-
if (!flushResult) return false;
3930+
if (flushResult == TryAgainResult.Canceled) return false;
39323931
}
39333932

3934-
bool stateSaveResult = this.DoWithTryAgainBox(AutoSaveStateIfConfigured, "Failed to auto-save state.");
3935-
if (!stateSaveResult) return false;
3933+
TryAgainResult stateSaveResult = this.DoWithTryAgainBox(AutoSaveStateIfConfigured, "Failed to auto-save state.");
3934+
if (stateSaveResult == TryAgainResult.Canceled) return false;
39363935

39373936
StopAv();
39383937

0 commit comments

Comments
 (0)