Skip to content

Commit da9aead

Browse files
committed
Added client side of save game import
1 parent 4bc1911 commit da9aead

11 files changed

Lines changed: 242 additions & 6 deletions

File tree

InsightLogParser.Client/Cetus/CetusClient.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,31 @@ public void Dispose()
198198
return default;
199199
}
200200
}
201+
202+
public async Task<ImportSaveResponse?> ImportSaveGameAsync(Stream saveGameStream)
203+
{
204+
//There is no retry with this one, so get a fresh token
205+
var authResult = await RefreshAuthAsync().ConfigureAwait(ConfigureAwaitOptions.None);
206+
if (!authResult) return default;
207+
208+
var requestUri = "api/v1/sync/syncWithSave";
209+
210+
try
211+
{
212+
var content = new StreamContent(saveGameStream);
213+
var result = await _httpClient.PostAsync(requestUri, content)
214+
.ConfigureAwait(ConfigureAwaitOptions.None);
215+
_messageWriter.WriteDebug($"CETUS: Got a {(int)result.StatusCode}-{result.StatusCode}");
216+
if (!result.IsSuccessStatusCode)
217+
{
218+
return default;
219+
}
220+
return await result.Content.ReadFromJsonAsync<ImportSaveResponse>().ConfigureAwait(ConfigureAwaitOptions.None);
221+
}
222+
catch (Exception e)
223+
{
224+
_messageWriter.WriteDebug($"CETUS: Exception: {e}");
225+
return default;
226+
}
227+
}
201228
}

InsightLogParser.Client/Cetus/DummyCetusClient.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ internal class DummyCetusClient : ICetusClient
1818
return Task.FromResult<ScreenshotResponse?>(null);
1919
}
2020

21+
public Task<ImportSaveResponse?> ImportSaveGameAsync(Stream saveGameStream)
22+
{
23+
return Task.FromResult<ImportSaveResponse?>(null);
24+
}
25+
2126
public Task<PuzzleStatusResponse?> GetPuzzleStatusAsync(PuzzleStatusRequest request)
2227
{
2328
return Task.FromResult<PuzzleStatusResponse?>(null);

InsightLogParser.Client/Cetus/ICetusClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal interface ICetusClient : IDisposable
1919
Task<SeenResponse?> PostSeenAsync(PlayerReport request);
2020
Task<SolvedResponse?> PostSolvedAsync(PlayerReport request);
2121
Task<ScreenshotResponse?> PostScreenshotAsync(Screenshot screenshot);
22+
Task<ImportSaveResponse?> ImportSaveGameAsync(Stream saveGameStream);
2223

2324
//"Search" operations
2425
Task<PuzzleStatusResponse?> GetPuzzleStatusAsync(PuzzleStatusRequest request);

InsightLogParser.Client/Configuration.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ namespace InsightLogParser.Client;
33

44
public class Configuration
55
{
6-
public static readonly int CurrentConfigurationVersion = 2;
6+
public static readonly int CurrentConfigurationVersion = 3;
77

88
public int ConfigVersion { get; set; } = CurrentConfigurationVersion;
99

1010
public string? ForceLogFolder { get; set; }
1111
public string? ForceGameRootFolder { get; set; }
1212
public string? ForcedParsedDatabasePath { get; set; }
1313
public string? ForceScreenshotFolder { get; set; }
14+
public string? ForcedOfflineSaveFile { get; set; }
15+
1416
public bool MonitorScreenshots { get; set; } = true;
1517
public bool ConfirmScreenshotDelete { get; set; } = true;
1618
public bool DeleteOnQuickUpload { get; set; } = false;

InsightLogParser.Client/Menu/AdvancedMenu.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ internal class AdvancedMenu : IMenu
66
{
77
private readonly UserComputer _computer;
88
private readonly MessageWriter _writer;
9+
private readonly Spider _spider;
910

10-
public AdvancedMenu(UserComputer computer, MessageWriter writer)
11+
public AdvancedMenu(UserComputer computer, MessageWriter writer, Spider spider)
1112
{
1213
_computer = computer;
1314
_writer = writer;
15+
_spider = spider;
1416
}
1517

1618
public IEnumerable<(char? key, string text)> MenuOptions
@@ -19,6 +21,10 @@ public AdvancedMenu(UserComputer computer, MessageWriter writer)
1921
{
2022
yield return (null, "Advanced or risky operations. Press [h] to display this menu again and backspace to back");
2123
yield return ('c', "Cleanup steam screenshot.vdf file (at your own risk)");
24+
if (_spider.IsOnline())
25+
{
26+
yield return ('u', "upload your offline progress to import solved puzzles");
27+
}
2228
}
2329
}
2430

@@ -29,6 +35,9 @@ public async Task<MenuResult> HandleOptionAsync(char keyChar)
2935
case 'c':
3036
ConfirmSteamScreenshotCleanup();
3137
return MenuResult.PrintMenu;
38+
case 'u':
39+
await ConfirmOfflineSaveUpload();
40+
return MenuResult.PrintMenu;
3241
default:
3342
return MenuResult.NotValidOption;
3443
}
@@ -51,4 +60,22 @@ private void ConfirmSteamScreenshotCleanup()
5160

5261
_computer.CleanupSteamScreenshotFile();
5362
}
63+
64+
private async Task ConfirmOfflineSaveUpload()
65+
{
66+
_writer.ConfirmOfflineSaveUpload();
67+
for (var i = 0; i < 3; i++)
68+
{
69+
var pressedKey = Console.ReadKey(false);
70+
if (pressedKey.Key != ConsoleKey.Y)
71+
{
72+
_writer.WriteLine("");
73+
_writer.WriteInfo("Cancelled");
74+
return;
75+
}
76+
_writer.WriteLine("");
77+
}
78+
79+
await _spider.ImportSaveGameAsync();
80+
}
5481
}

InsightLogParser.Client/Menu/RootMenu.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public async Task<MenuResult> HandleOptionAsync(char keyChar)
5555
_menuHandler.EnterMenu(new RouteMenu(_menuHandler, _spider));
5656
return MenuResult.Ok;
5757
case 'a':
58-
_menuHandler.EnterMenu(new AdvancedMenu(_computer, _writer));
58+
_menuHandler.EnterMenu(new AdvancedMenu(_computer, _writer, _spider));
5959
return MenuResult.Ok;
6060
case 'C':
6161
_menuHandler.EnterMenu(new CheeseMenu(_spider, _writer));

InsightLogParser.Client/MessageWriter.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,19 @@ public void WriteLogLine(string logLine)
6969
}
7070
}
7171

72-
public void WriteInfo(string infoLine)
72+
public void WriteInfo(string infoLine, ConsoleColor color)
7373
{
7474
lock (_lock)
7575
{
76-
Console.ForegroundColor = ConsoleColor.DarkGreen;
76+
Console.ForegroundColor = color;
7777
Console.Write("[INFO]");
7878
Console.ResetColor();
7979
Console.WriteLine($": {infoLine}");
8080
}
8181
}
8282

83+
public void WriteInfo(string infoLine) => WriteInfo(infoLine, ConsoleColor.DarkGreen);
84+
8385
public void WriteDebug(string debugMessage)
8486
{
8587
if (!ShouldShowDebug()) return;
@@ -471,6 +473,22 @@ public void ConfirmScreenshotCleanup()
471473
}
472474
}
473475

476+
public void ConfirmOfflineSaveUpload()
477+
{
478+
lock (_lock)
479+
{
480+
Console.ForegroundColor = ConsoleColor.Red;
481+
Console.WriteLine("This will upload your offline save to extract solved puzzles from it");
482+
Console.WriteLine("The server will add solves for all relevant solved puzzles from your offline save");
483+
Console.WriteLine("Additionally it will REMOVE any solved puzzles not marked as solved in your offline save that are dated EARLIER than the MOST RECENT solved puzzle in your offline save");
484+
Console.WriteLine("This will clean up any stale solves you might have that are not actually counted by the game");
485+
Console.WriteLine("Please be aware that just because it works for me does not mean it will work for you");
486+
Console.WriteLine("*** Before using this, make sure that your offline save file is fully up to date with your online save ***");
487+
Console.WriteLine("At your own risk, press [y] three times to proceed or any other key to cancel");
488+
Console.ResetColor();
489+
}
490+
}
491+
474492
public void WriteClosest(IEnumerable<DistanceModel> distanceModels, bool containsCetusInfo)
475493
{
476494
lock (_lock)

InsightLogParser.Client/ParsedDatabase.cs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Globalization;
22
using System.Text;
33
using System.Text.Json;
4+
using InsightLogParser.Common.ApiModels;
45
using InsightLogParser.Common.ParsedDbModels;
56
using InsightLogParser.Common.PuzzleParser;
67
using InsightLogParser.Common.World;
@@ -317,7 +318,74 @@ public void RemoveNonWorldPuzzles(GamePuzzleHandler puzzleHandler)
317318
}
318319
}
319320

320-
private void MakeBackup()
321+
public void ImportSaves(ImportSolve[] importedSolves, HashSet<int> allSolvedIds, DateTimeOffset mostRecentSolveTime)
322+
{
323+
lock (_lock)
324+
{
325+
foreach (var puzzleZone in _db.Zones.Keys)
326+
{
327+
var zoneNode = _db.Zones[puzzleZone];
328+
foreach (var type in zoneNode.SolvedEntries.Keys)
329+
{
330+
var localSolves = zoneNode.SolvedEntries[type];
331+
332+
//Add new solves
333+
var imported = importedSolves
334+
.Where(x => x.PuzzleZone == puzzleZone && x.PuzzleType == type)
335+
.ToList();
336+
var importedCounter = 0;
337+
foreach (var importSolve in imported)
338+
{
339+
if (!localSolves.ContainsKey(importSolve.PuzzleId))
340+
{
341+
localSolves.Add(importSolve.PuzzleId, new PuzzleNode
342+
{
343+
Solves = [importSolve.SolvedAt]
344+
});
345+
_dirty = true;
346+
importedCounter++;
347+
}
348+
}
349+
350+
//Remove stale solves
351+
var stales = new List<(int puzzleId, PuzzleNode node)>();
352+
foreach (var (puzzleId, node) in localSolves)
353+
{
354+
//If it is solved, it is not stale
355+
if (allSolvedIds.Contains(puzzleId)) continue;
356+
357+
//If we have a solve after the most recent time, we can't say if it's stale or not
358+
if (node.Solves.Any(x => x >= mostRecentSolveTime)) continue;
359+
360+
stales.Add((puzzleId, node));
361+
}
362+
363+
foreach (var valueTuple in stales)
364+
{
365+
localSolves.Remove(valueTuple.puzzleId);
366+
_dirty = true;
367+
}
368+
var puzzleName = WorldInformation.GetPuzzleName(type);
369+
var zoneName = WorldInformation.GetZoneName(puzzleZone);
370+
371+
if (importedCounter > 0) _messageWriter.WriteInfo($"Imported {importedCounter} solves of {puzzleName} from {zoneName} to local db", ConsoleColor.Green);
372+
if (stales.Count > 0) _messageWriter.WriteInfo($"Removed {stales.Count} stale local solves of {puzzleName} from {zoneName}", ConsoleColor.Yellow);
373+
}
374+
}
375+
376+
if (_dirty)
377+
{
378+
var backupPath = MakeBackup();
379+
_messageWriter.WriteInfo($"Changes will be made to the local db, a backup has been saved at {backupPath}", ConsoleColor.Yellow);
380+
}
381+
else
382+
{
383+
_messageWriter.WriteInfo("Local db was already up to date");
384+
}
385+
}
386+
}
387+
388+
private string MakeBackup()
321389
{
322390
var pathPart = Path.GetDirectoryName(_jsonPath);
323391
if (string.IsNullOrWhiteSpace(pathPart)) pathPart = ".";
@@ -326,6 +394,7 @@ private void MakeBackup()
326394
var timestring = DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture);
327395
var backupName = $"{filePart}-backup-{timestring}{extensionPart}";
328396
File.Copy(_jsonPath, backupName);
397+
return backupName;
329398
}
330399

331400
public bool IsSolved(int puzzleKrakenId)

InsightLogParser.Client/Spider.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,43 @@ public void TargetOtherMatchbox()
485485
_teleportManager.SetTarget(furthest);
486486
}
487487

488+
public async Task ImportSaveGameAsync()
489+
{
490+
var saveFilePath = _computer.GetOfflineSaveFile();
491+
if (!File.Exists(saveFilePath))
492+
{
493+
_messageWriter.WriteError($"Could not find file '{saveFilePath}'");
494+
return;
495+
}
496+
497+
ImportSaveResponse? response;
498+
await using (var fs = File.OpenRead(saveFilePath))
499+
{
500+
response = await _cetusClient.ImportSaveGameAsync(fs);
501+
}
502+
503+
if (response == null)
504+
{
505+
_messageWriter.WriteError("Something went wrong");
506+
return;
507+
}
508+
509+
var allSolvedIds = response
510+
.AllSolves
511+
.OrderBy(x => x)
512+
.Select(x => x.ToString());
513+
514+
const string solvedPuzzleIdFile = "solvedpuzzleids.txt";
515+
_messageWriter.WriteInfo($"Saving {response.AllSolves.Length} solved puzzle ids to " + solvedPuzzleIdFile);
516+
await File.WriteAllTextAsync(solvedPuzzleIdFile, string.Join(",", allSolvedIds));
517+
518+
_messageWriter.WriteInfo($"Server flagged {response.ImportedSolves.Length} previously unsolved puzzles as solved");
519+
_messageWriter.WriteInfo($"Server flagged {response.RemovedStales.Length} previously solved puzzles as unsolved as they were stale");
520+
521+
//Update local db
522+
_db.ImportSaves(response.ImportedSolves, response.AllSolves.ToHashSet(), response.MostRecentSolve);
523+
}
524+
488525
public async Task ListClosestAsync(int numberToList, bool use2dDistance)
489526
{
490527
var lastTeleport = _teleportManager.GetLastTeleport();

InsightLogParser.Client/UserComputer.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ public string GetPrimaryLogFile()
6969
return logFile;
7070
}
7171

72+
public string GetOfflineSaveFile()
73+
{
74+
if (!string.IsNullOrWhiteSpace(_configuration.ForcedOfflineSaveFile))
75+
{
76+
return _configuration.ForcedOfflineSaveFile;
77+
}
78+
else
79+
{
80+
var appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
81+
return Path.Combine(appDataFolder, "IslandsofInsight", "Saved", "SaveGames", "OfflineSavegame.sav");
82+
}
83+
}
84+
7285
private string[]? FindSteamGameLibraryLocations()
7386
{
7487
//Find the steam install directory in the registry

0 commit comments

Comments
 (0)