Skip to content

Commit 7685158

Browse files
committed
Refactor persistence
1 parent 66035d8 commit 7685158

4 files changed

Lines changed: 105 additions & 158 deletions

File tree

CSharpCodeAnalyst/App.xaml.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ private void StartUi()
109109
var refactoringService = new RefactoringService(refactoringInteraction, messaging);
110110
mainWindow.SetViewer(explorationGraphViewer, messaging);
111111

112-
var projectStorage = new JsonProjectStorage(uiNotification);
113-
var projectService = new ProjectService(projectStorage, userSettings);
112+
var projectStorage = new JsonProjectStorage();
113+
var projectService = new ProjectService(projectStorage, uiNotification, userSettings);
114114

115115
var viewModel = new MainViewModel(messaging, applicationSettings, userSettings, analyzerManager, refactoringService, projectService);
116116
var graphViewModel = new GraphViewModel(explorationGraphViewer, explorer, messaging, applicationSettings, refactoringService);
Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,29 @@
1-
using CSharpCodeAnalyst.Features.Import;
21
using CSharpCodeAnalyst.Persistence.Dto;
32
using CSharpCodeAnalyst.Shared;
43

54
namespace CSharpCodeAnalyst.Persistence.Contracts;
65

76
/// <summary>
8-
/// Low-level persistence contract: serializes and deserializes project data to/from a storage medium.
9-
/// Implementations are storage-technology specific (e.g. JSON files, databases).
10-
/// Does not own application state, dirty tracking, or MRU management.
7+
/// Low-level persistence contract: serializes and deserializes project data to/from a
8+
/// storage medium. Implementations are storage-technology specific (e.g. JSON files).
9+
/// Has no UI dependency — no file dialogs, no notifications, no events.
10+
/// Callers are responsible for providing file paths and handling user feedback.
1111
/// </summary>
1212
public interface IProjectStorage
1313
{
1414
bool HasSnapshot { get; }
1515

16-
event EventHandler<ImportStateChangedArgs>? LoadingStateChanged;
16+
/// <summary>Loads project data from the given file path.</summary>
17+
Task<Result<ProjectData>> LoadFromFileAsync(string filePath);
1718

18-
/// <summary>
19-
/// Shows an open-file dialog and loads the selected project.
20-
/// </summary>
21-
Task<Result<(string fileName, ProjectData data)>> LoadAsync();
22-
23-
/// <summary>
24-
/// Loads a project directly from the given file path without showing a dialog.
25-
/// </summary>
26-
Task<Result<(string fileName, ProjectData data)>> LoadFromFileAsync(string filePath);
19+
/// <summary>Saves project data to the given file path.</summary>
20+
Result SaveToFile(ProjectData data, string filePath);
2721

28-
/// <summary>
29-
/// Saves the project. If <paramref name="currentFilePath" /> is provided the file is
30-
/// overwritten in place; otherwise a save-file dialog is shown.
31-
/// </summary>
32-
Result<string> Save(ProjectData data, string? currentFilePath = null);
33-
34-
/// <summary>
35-
/// Stores an in-memory snapshot of the given project data.
36-
/// </summary>
22+
/// <summary>Stores an in-memory snapshot of the given project data.</summary>
3723
void CreateSnapshot(ProjectData data);
3824

3925
/// <summary>
40-
/// Restores the latest snapshot by invoking <paramref name="restoreAction" /> with the
41-
/// stored data. Shows a warning when no snapshot is available.
26+
/// Returns the latest snapshot, or a failure result when none exists.
4227
/// </summary>
43-
void RestoreSnapshot(Action<ProjectData> restoreAction);
28+
Result<ProjectData> RestoreSnapshot();
4429
}
Lines changed: 13 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,65 @@
11
using System.IO;
22
using System.Text.Json;
33
using System.Text.Json.Serialization;
4-
using CSharpCodeAnalyst.Features.Import;
54
using CSharpCodeAnalyst.Persistence.Contracts;
65
using CSharpCodeAnalyst.Persistence.Dto;
7-
using CSharpCodeAnalyst.Resources;
86
using CSharpCodeAnalyst.Shared;
9-
using CSharpCodeAnalyst.Shared.Notifications;
107

118
namespace CSharpCodeAnalyst.Persistence.Json;
129

1310
/// <summary>
1411
/// JSON-file based implementation of <see cref="IProjectStorage" />.
12+
/// Pure I/O — no UI dependency, no file dialogs, no notifications, no events.
1513
/// </summary>
1614
public class JsonProjectStorage : IProjectStorage
1715
{
18-
private readonly IUserNotification _ui;
1916
private ProjectData? _snapshot;
2017

21-
public JsonProjectStorage(IUserNotification ui)
22-
{
23-
_ui = ui;
24-
}
25-
2618
public bool HasSnapshot => _snapshot != null;
2719

28-
public event EventHandler<ImportStateChangedArgs>? LoadingStateChanged;
29-
30-
/// <inheritdoc />
31-
public async Task<Result<(string fileName, ProjectData data)>> LoadAsync()
32-
{
33-
var fileName = _ui.ShowOpenFileDialog("JSON files (*.json)|*.json", "Load Project");
34-
if (string.IsNullOrEmpty(fileName))
35-
{
36-
return Result<(string, ProjectData)>.Canceled();
37-
}
38-
39-
return await LoadFromFileAsync(fileName);
40-
}
41-
4220
/// <inheritdoc />
43-
public async Task<Result<(string fileName, ProjectData data)>> LoadFromFileAsync(string filePath)
21+
public async Task<Result<ProjectData>> LoadFromFileAsync(string filePath)
4422
{
4523
try
4624
{
47-
OnLoadingStateChanged("Loading...", true);
48-
4925
var projectData = await Task.Run(() => DeserializeProject(filePath));
50-
return Result<(string, ProjectData)>.Success((filePath, projectData));
26+
return Result<ProjectData>.Success(projectData);
5127
}
5228
catch (Exception ex)
5329
{
54-
ShowError(ex);
55-
return Result<(string, ProjectData)>.Failure(ex);
56-
}
57-
finally
58-
{
59-
OnLoadingStateChanged(string.Empty, false);
30+
return Result<ProjectData>.Failure(ex);
6031
}
6132
}
6233

6334
/// <inheritdoc />
64-
public Result<string> Save(ProjectData data, string? currentFilePath = null)
35+
public Result SaveToFile(ProjectData data, string filePath)
6536
{
66-
if (!TryGetSaveFilePath(currentFilePath, out var filePath))
67-
{
68-
return Result<string>.Canceled();
69-
}
70-
7137
try
7238
{
7339
SerializeProject(data, filePath);
74-
return Result<string>.Success(filePath);
40+
return Result.Success();
7541
}
7642
catch (Exception ex)
7743
{
78-
ShowError(ex);
79-
return Result<string>.Failure(ex);
44+
return Result.Failure(ex);
8045
}
8146
}
8247

8348
/// <inheritdoc />
8449
public void CreateSnapshot(ProjectData data)
8550
{
8651
_snapshot = data;
87-
_ui.ShowSuccess(Strings.Snapshot_Success);
8852
}
8953

9054
/// <inheritdoc />
91-
public void RestoreSnapshot(Action<ProjectData> restoreAction)
55+
public Result<ProjectData> RestoreSnapshot()
9256
{
93-
if (!HasSnapshot)
57+
if (_snapshot is null)
9458
{
95-
_ui.ShowWarning(Strings.Restore_NoSnapshot);
96-
return;
59+
return Result<ProjectData>.Failure(new InvalidOperationException("No snapshot available."));
9760
}
9861

99-
try
100-
{
101-
OnLoadingStateChanged(Strings.Restore_LoadMessage, true);
102-
restoreAction(_snapshot!);
103-
_ui.ShowSuccess(Strings.Restore_Success);
104-
}
105-
catch (Exception ex)
106-
{
107-
ShowError(ex);
108-
}
109-
finally
110-
{
111-
OnLoadingStateChanged(string.Empty, false);
112-
}
113-
}
114-
115-
private void OnLoadingStateChanged(string message, bool isLoading)
116-
{
117-
LoadingStateChanged?.Invoke(this, new ImportStateChangedArgs(message, isLoading));
118-
}
119-
120-
private void ShowError(Exception ex)
121-
{
122-
var message = string.Format(Strings.OperationFailed_Message, ex.Message);
123-
_ui.ShowError(message);
62+
return Result<ProjectData>.Success(_snapshot);
12463
}
12564

12665
private static ProjectData DeserializeProject(string filePath)
@@ -136,30 +75,8 @@ private static ProjectData DeserializeProject(string filePath)
13675

13776
private static void SerializeProject(ProjectData projectData, string filePath)
13877
{
139-
var options = new JsonSerializerOptions
140-
{
141-
WriteIndented = false
142-
};
78+
var options = new JsonSerializerOptions { WriteIndented = false };
14379
var json = JsonSerializer.Serialize(projectData, options);
14480
File.WriteAllText(filePath, json);
14581
}
146-
147-
private bool TryGetSaveFilePath(string? currentFilePath, out string outFileName)
148-
{
149-
if (!string.IsNullOrEmpty(currentFilePath))
150-
{
151-
outFileName = currentFilePath;
152-
return true;
153-
}
154-
155-
var fileName = _ui.ShowSaveFileDialog("JSON files (*.json)|*.json", "Save Project");
156-
if (string.IsNullOrEmpty(fileName))
157-
{
158-
outFileName = string.Empty;
159-
return false;
160-
}
161-
162-
outFileName = fileName;
163-
return true;
164-
}
165-
}
82+
}

0 commit comments

Comments
 (0)