Skip to content
Draft
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
27 changes: 20 additions & 7 deletions PolyPilot/Models/ConnectionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ private static ConnectionSettings DefaultSettings()
#endif
}

public void Save()
public bool Save()
{
try
{
Expand All @@ -272,8 +272,9 @@ public void Save()
var json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true });
#endif
File.WriteAllText(SettingsPath, json);
return true;
}
catch { }
catch { return false; }
}

#if MACCATALYST
Expand Down Expand Up @@ -308,12 +309,13 @@ private static void RecoverSecretsFromSecureStorage(ConnectionSettings settings)

if (needsSave)
{
settings.Save();
bool saved = settings.Save();

// Per-key cleanup: only remove a Keychain entry if that specific value was recovered
// and Save() wrote the file. Prevents data loss if Keychain read fails transiently
// for one secret but succeeds for another.
if (File.Exists(SettingsPath))
// and Save() succeeded. Using the bool return value is stronger than File.Exists():
// if Save() fails (e.g., disk full) but a prior file exists, File.Exists would return
// true and we'd delete Keychain entries without having persisted the recovered values.
if (saved)
{
if (recoveredRemote)
try { SecureStorage.Default.Remove("polypilot.connection.remoteToken"); } catch { }
Expand All @@ -324,11 +326,22 @@ private static void RecoverSecretsFromSecureStorage(ConnectionSettings settings)
}
}
}
catch { }
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[ConnectionSettings] RecoverSecretsFromSecureStorage failed: {ex.Message}");
try
{
var logPath = Path.Combine(GetPolyPilotDir(), "crash.log");
File.AppendAllText(logPath, $"\n=== {DateTime.Now:yyyy-MM-dd HH:mm:ss} [RecoverSecretsFromSecureStorage] ===\n{ex}\n");
}
catch { /* Don't throw in exception handler */ }
}
}

private static string? ReadSecureStorage(string key)
{
// Intentional sync-over-async: Task.Run avoids SynchronizationContext deadlock
// on the UI thread while keeping this one-time migration path synchronous.
try { return Task.Run(() => SecureStorage.Default.GetAsync(key)).GetAwaiter().GetResult(); }
catch { return null; }
}
Expand Down