Skip to content

Commit eb6e72c

Browse files
authored
Feature/persistence refactoring (#69)
* Step 1: Introduce interfaces * Step 2: Introduced typed project settings * Step 3: ProjectService * Step 4 unify settings * Clarifications in settings dialog
1 parent b1f9aa0 commit eb6e72c

20 files changed

Lines changed: 576 additions & 324 deletions

CSharpCodeAnalyst/App.xaml.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using CSharpCodeAnalyst.CommandLine;
66
using CSharpCodeAnalyst.Configuration;
77
using CSharpCodeAnalyst.Features.AdvancedSearch;
8+
using CSharpCodeAnalyst.Persistence.Json;
89
using CSharpCodeAnalyst.Features.Analyzers;
910
using CSharpCodeAnalyst.Features.Graph;
1011
using CSharpCodeAnalyst.Features.Info;
@@ -84,14 +85,14 @@ private void StartUi()
8485
.AddJsonFile("appsettings.json", false, true);
8586

8687
IConfiguration configuration = builder.Build();
87-
var applicationSettings = configuration.GetSection("ApplicationSettings").Get<ApplicationSettings>();
88+
var applicationSettings = configuration.GetSection("ApplicationSettings").Get<AppSettings>();
8889

8990
if (applicationSettings is null)
9091
{
91-
applicationSettings = new ApplicationSettings();
92+
applicationSettings = new AppSettings();
9293
}
9394

94-
var userSettings = UserSettings.Instance;
95+
var userSettings = UserPreferences.LoadOrCreate();
9596

9697
var uiNotification = new WindowsUserNotification();
9798
var messaging = new MessageBus();
@@ -107,7 +108,11 @@ private void StartUi()
107108
var refactoringInteraction = new RefactoringInteraction();
108109
var refactoringService = new RefactoringService(refactoringInteraction, messaging);
109110
mainWindow.SetViewer(explorationGraphViewer, messaging);
110-
var viewModel = new MainViewModel(messaging, applicationSettings, userSettings, analyzerManager, refactoringService);
111+
112+
var projectStorage = new JsonProjectStorage(uiNotification);
113+
var projectService = new ProjectService(projectStorage, uiNotification, userSettings);
114+
115+
var viewModel = new MainViewModel(messaging, applicationSettings, userSettings, analyzerManager, refactoringService, projectService);
111116
var graphViewModel = new GraphViewModel(explorationGraphViewer, explorer, messaging, applicationSettings, refactoringService);
112117
var treeViewModel = new TreeViewModel(messaging, refactoringService);
113118
var searchViewModel = new AdvancedSearchViewModel(messaging, refactoringService);

CSharpCodeAnalyst/CommandLine/ConsoleValidationCommand.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public async Task<int> Execute()
4545
Initializer.InitializeMsBuildLocator();
4646

4747
// Parse solution and do analysis
48-
var settings = LoadApplicationSettings();
48+
var settings = LoadAppSettings();
4949
var graph = await ParseSolution(solutionFile, settings).ConfigureAwait(false);
5050
var violations = RunAnalysis(rulesFile, graph);
5151

@@ -64,15 +64,15 @@ public async Task<int> Execute()
6464
return resultCode;
6565
}
6666

67-
private static ApplicationSettings LoadApplicationSettings()
67+
private static AppSettings LoadAppSettings()
6868
{
6969
var builder = new ConfigurationBuilder()
7070
.SetBasePath(Directory.GetCurrentDirectory())
7171
.AddJsonFile("appsettings.json", false, true);
7272

7373
IConfiguration configuration = builder.Build();
74-
var settings = configuration.GetSection("ApplicationSettings").Get<ApplicationSettings>();
75-
settings ??= new ApplicationSettings();
74+
var settings = configuration.GetSection("ApplicationSettings").Get<AppSettings>();
75+
settings ??= new AppSettings();
7676
return settings;
7777
}
7878

@@ -86,7 +86,7 @@ private static List<Violation> RunAnalysis(string rulesFilePath, CodeGraph.Graph
8686
return violations;
8787
}
8888

89-
private static async Task<CodeGraph.Graph.CodeGraph> ParseSolution(string solutionPath, ApplicationSettings settings)
89+
private static async Task<CodeGraph.Graph.CodeGraph> ParseSolution(string solutionPath, AppSettings settings)
9090
{
9191
var filter = new ProjectExclusionRegExCollection();
9292
filter.Initialize(settings.DefaultProjectExcludeFilter);

CSharpCodeAnalyst/Configuration/ApplicationSettings.cs renamed to CSharpCodeAnalyst/Configuration/AppSettings.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
using System.IO;
1+
using System.IO;
22
using System.Text.Json;
33

44
namespace CSharpCodeAnalyst.Configuration;
55

6-
public class ApplicationSettings
6+
public class AppSettings
77
{
88
public int WarningCodeElementLimit { get; set; } = 300;
99

10-
1110
public string DefaultProjectExcludeFilter
1211
{
1312
get => CleanupProjectFilters(field);
@@ -33,15 +32,17 @@ public static string CleanupProjectFilters(string filterText)
3332

3433
public void Save(string appSettingsPath)
3534
{
35+
// Keep "ApplicationSettings" as the JSON section key for backward compatibility
36+
// with existing appsettings.json files.
3637
var root = new { ApplicationSettings = this };
3738
var options = new JsonSerializerOptions { WriteIndented = true };
3839
var json = JsonSerializer.Serialize(root, options);
3940
File.WriteAllText(appSettingsPath, json);
4041
}
4142

42-
public ApplicationSettings Clone()
43+
public AppSettings Clone()
4344
{
44-
return new ApplicationSettings
45+
return new AppSettings
4546
{
4647
WarningCodeElementLimit = this.WarningCodeElementLimit,
4748
DefaultProjectExcludeFilter = this.DefaultProjectExcludeFilter,
@@ -50,4 +51,4 @@ public ApplicationSettings Clone()
5051
WarnIfFiltersActive = this.WarnIfFiltersActive
5152
};
5253
}
53-
}
54+
}
Lines changed: 105 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Window x:Class="CSharpCodeAnalyst.Configuration.SettingsDialog"
1+
<Window x:Class="CSharpCodeAnalyst.Configuration.SettingsDialog"
22
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
44
xmlns:resources="clr-namespace:CSharpCodeAnalyst.Resources"
@@ -13,87 +13,115 @@
1313
<RowDefinition Height="Auto" />
1414
</Grid.RowDefinitions>
1515

16-
<ScrollViewer VerticalScrollBarVisibility="Auto">
17-
<StackPanel>
16+
<TabControl>
1817

19-
<TextBlock Margin="10 0 0 15" FontWeight="Bold" Foreground="Red"
20-
Text="{x:Static resources:Strings.Settings_Hint}" />
18+
<!-- ═══════════════════════════════════════════════════════
19+
Tab 1: Application Settings (appsettings.json)
20+
Shared across all users of this installation.
21+
════════════════════════════════════════════════════════ -->
22+
<TabItem Header="{x:Static resources:Strings.Settings_Tab_Application}">
23+
<ScrollViewer VerticalScrollBarVisibility="Auto">
24+
<StackPanel Margin="5,5,5,0">
2125

22-
<GroupBox Header="{x:Static resources:Strings.Settings_General}" Margin="5,0,0,0">
23-
<StackPanel Margin="10">
24-
<CheckBox x:Name="AutoAddContainingTypeCheckBox"
25-
Content="{x:Static resources:Strings.Settings_AddContainingType}"
26-
Margin="0,5"
27-
ToolTip="{x:Static resources:Strings.Settings_Settings_AddContainingType_Tooltip}" />
26+
<TextBlock Margin="5,5,5,3" Foreground="Gray"
27+
Text="{x:Static resources:Strings.Settings_Tab_Application_Hint}" />
2828

29-
<CheckBox x:Name="IncludeExternalCodeCheckBox"
30-
Content="{x:Static resources:Strings.Settings_IncludeExternalCode}"
31-
Margin="0,5"
32-
ToolTip="{x:Static resources:Strings.Settings_IncludeExternalCode_Tooltip}" />
29+
<TextBlock Margin="5,0,5,10" FontWeight="Bold" Foreground="Red"
30+
Text="{x:Static resources:Strings.Settings_Hint}" />
31+
32+
<GroupBox Header="{x:Static resources:Strings.Settings_General}" Margin="5,0,0,0">
33+
<StackPanel Margin="10">
34+
<CheckBox x:Name="AutoAddContainingTypeCheckBox"
35+
Content="{x:Static resources:Strings.Settings_AddContainingType}"
36+
Margin="0,5"
37+
ToolTip="{x:Static resources:Strings.Settings_Settings_AddContainingType_Tooltip}" />
38+
39+
<CheckBox x:Name="IncludeExternalCodeCheckBox"
40+
Content="{x:Static resources:Strings.Settings_IncludeExternalCode}"
41+
Margin="0,5"
42+
ToolTip="{x:Static resources:Strings.Settings_IncludeExternalCode_Tooltip}" />
43+
44+
<CheckBox x:Name="WarnIfFiltersActiveCheckBox"
45+
Content="{x:Static resources:Strings.Settings_WarnIfFiltersActive}"
46+
Margin="0,5"
47+
ToolTip="{x:Static resources:Strings.Settings_WarnIfFiltersActive_Tooltip}" />
48+
</StackPanel>
49+
</GroupBox>
50+
51+
<GroupBox Header="{x:Static resources:Strings.Settings_Performance}" Margin="5,5,0,0">
52+
<StackPanel Margin="10">
53+
<Label Margin="0 0 0 3"
54+
Content="{x:Static resources:Strings.Settings_CodeElementWarningLimit}" />
55+
<TextBox x:Name="WarningLimitTextBox"
56+
Width="100"
57+
HorizontalAlignment="Left"
58+
Margin="0,0,0,10"
59+
ToolTip="{x:Static resources:Strings.Settings_CodeElementWarningLimit_Tooltip}" />
60+
</StackPanel>
61+
</GroupBox>
62+
63+
<GroupBox Header="{x:Static resources:Strings.Settings_ProjectFilters}" Margin="5,5,0,0">
64+
<StackPanel Margin="10">
65+
<Label Margin="0 0 0 3" Content="{x:Static resources:Strings.Settings_ProjectFilters_Text}" />
66+
<TextBox x:Name="ProjectExcludeFilterTextBox"
67+
Height="80"
68+
AcceptsReturn="True"
69+
TextWrapping="Wrap"
70+
VerticalScrollBarVisibility="Auto"
71+
HorizontalAlignment="Stretch"
72+
Margin="0,0,0,10"
73+
ToolTip="{x:Static resources:Strings.Settings_ProjectFilters_Tooltip}" />
74+
</StackPanel>
75+
</GroupBox>
3376

34-
<CheckBox x:Name="WarnIfFiltersActiveCheckBox"
35-
Content="{x:Static resources:Strings.Settings_WarnIfFiltersActive}"
36-
Margin="0,5"
37-
ToolTip="{x:Static resources:Strings.Settings_WarnIfFiltersActive_Tooltip}" />
38-
</StackPanel>
39-
</GroupBox>
40-
41-
<GroupBox Header="{x:Static resources:Strings.Settings_Performance}" Margin="5,0,0,0">
42-
<StackPanel Margin="10">
43-
<Label Margin="0 0 0 3"
44-
Content="{x:Static resources:Strings.Settings_CodeElementWarningLimit}" />
45-
<TextBox x:Name="WarningLimitTextBox"
46-
Width="100"
47-
HorizontalAlignment="Left"
48-
Margin="0,0,0,10"
49-
ToolTip="{x:Static resources:Strings.Settings_CodeElementWarningLimit_Tooltip}" />
50-
</StackPanel>
51-
</GroupBox>
52-
53-
<GroupBox Header="{x:Static resources:Strings.Settings_ProjectFilters}" Margin="5,0,0,0">
54-
<StackPanel Margin="10">
55-
<Label Margin="0 0 0 3" Content="{x:Static resources:Strings.Settings_ProjectFilters_Text}" />
56-
<TextBox x:Name="ProjectExcludeFilterTextBox"
57-
Height="80"
58-
59-
AcceptsReturn="True"
60-
TextWrapping="Wrap"
61-
VerticalScrollBarVisibility="Auto"
62-
HorizontalAlignment="Stretch"
63-
Margin="0,0,0,10"
64-
ToolTip="{x:Static resources:Strings.Settings_ProjectFilters_Tooltip}" />
6577
</StackPanel>
66-
</GroupBox>
67-
68-
<GroupBox Header="{x:Static resources:Strings.Settings_AiAdvisor}" Margin="5,5,0,0">
69-
<StackPanel Margin="10">
70-
<Label Margin="0 0 0 3"
71-
Content="{x:Static resources:Strings.Settings_AiEndpoint}"
72-
ToolTip="{x:Static resources:Strings.Settings_AiEndpoint_Tooltip}" />
73-
<TextBox x:Name="AiEndpointTextBox"
74-
HorizontalAlignment="Stretch"
75-
Margin="0,0,0,8"
76-
ToolTip="{x:Static resources:Strings.Settings_AiEndpoint_Tooltip}" />
77-
78-
<Label Margin="0 0 0 3"
79-
Content="{x:Static resources:Strings.Settings_AiModel}"
80-
ToolTip="{x:Static resources:Strings.Settings_AiModel_Tooltip}" />
81-
<TextBox x:Name="AiModelTextBox"
82-
HorizontalAlignment="Stretch"
83-
Margin="0,0,0,8"
84-
ToolTip="{x:Static resources:Strings.Settings_AiModel_Tooltip}" />
85-
86-
<Label Margin="0 0 0 3"
87-
Content="{x:Static resources:Strings.Settings_AiApiKey}"
88-
ToolTip="{x:Static resources:Strings.Settings_AiApiKey_Tooltip}" />
89-
<PasswordBox x:Name="AiApiKeyBox"
90-
HorizontalAlignment="Stretch"
91-
Margin="0,0,0,8"
92-
ToolTip="{x:Static resources:Strings.Settings_AiApiKey_Tooltip}" />
78+
</ScrollViewer>
79+
</TabItem>
80+
81+
<!-- ═══════════════════════════════════════════════════════
82+
Tab 2: User Preferences (%LocalAppData%)
83+
Stored per user, not shared across the installation.
84+
════════════════════════════════════════════════════════ -->
85+
<TabItem Header="{x:Static resources:Strings.Settings_Tab_User}">
86+
<ScrollViewer VerticalScrollBarVisibility="Auto">
87+
<StackPanel Margin="5,5,5,0">
88+
89+
<TextBlock Margin="5,5,5,10" Foreground="Gray"
90+
Text="{x:Static resources:Strings.Settings_Tab_User_Hint}" />
91+
92+
<GroupBox Header="{x:Static resources:Strings.Settings_AiAdvisor}" Margin="5,0,0,0">
93+
<StackPanel Margin="10">
94+
<Label Margin="0 0 0 3"
95+
Content="{x:Static resources:Strings.Settings_AiEndpoint}"
96+
ToolTip="{x:Static resources:Strings.Settings_AiEndpoint_Tooltip}" />
97+
<TextBox x:Name="AiEndpointTextBox"
98+
HorizontalAlignment="Stretch"
99+
Margin="0,0,0,8"
100+
ToolTip="{x:Static resources:Strings.Settings_AiEndpoint_Tooltip}" />
101+
102+
<Label Margin="0 0 0 3"
103+
Content="{x:Static resources:Strings.Settings_AiModel}"
104+
ToolTip="{x:Static resources:Strings.Settings_AiModel_Tooltip}" />
105+
<TextBox x:Name="AiModelTextBox"
106+
HorizontalAlignment="Stretch"
107+
Margin="0,0,0,8"
108+
ToolTip="{x:Static resources:Strings.Settings_AiModel_Tooltip}" />
109+
110+
<Label Margin="0 0 0 3"
111+
Content="{x:Static resources:Strings.Settings_AiApiKey}"
112+
ToolTip="{x:Static resources:Strings.Settings_AiApiKey_Tooltip}" />
113+
<PasswordBox x:Name="AiApiKeyBox"
114+
HorizontalAlignment="Stretch"
115+
Margin="0,0,0,8"
116+
ToolTip="{x:Static resources:Strings.Settings_AiApiKey_Tooltip}" />
117+
</StackPanel>
118+
</GroupBox>
119+
93120
</StackPanel>
94-
</GroupBox>
95-
</StackPanel>
96-
</ScrollViewer>
121+
</ScrollViewer>
122+
</TabItem>
123+
124+
</TabControl>
97125

98126
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
99127
<Button Content="{x:Static resources:Strings.Default}" Width="75" Click="DefaultsButton_Click"
@@ -104,4 +132,4 @@
104132
IsCancel="True" />
105133
</StackPanel>
106134
</Grid>
107-
</Window>
135+
</Window>

CSharpCodeAnalyst/Configuration/SettingsDialog.xaml.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ namespace CSharpCodeAnalyst.Configuration;
88
public partial class SettingsDialog
99
{
1010

11-
public SettingsDialog(ApplicationSettings applSettings, UserSettings userSettings)
11+
public SettingsDialog(AppSettings applSettings, UserPreferences userSettings)
1212
{
1313
InitializeComponent();
14-
UserSettings = userSettings.Clone();
14+
UserPreferences = userSettings.Clone();
1515
AppSettings = applSettings.Clone();
1616
LoadSettingsToUi();
1717
}
1818

19-
public UserSettings UserSettings { get; }
19+
public UserPreferences UserPreferences { get; }
2020

21-
public ApplicationSettings AppSettings { get; private set; }
21+
public AppSettings AppSettings { get; private set; }
2222

2323
private void LoadSettingsToUi()
2424
{
@@ -30,8 +30,8 @@ private void LoadSettingsToUi()
3030
IncludeExternalCodeCheckBox.IsChecked = AppSettings.IncludeExternalCode;
3131
WarnIfFiltersActiveCheckBox.IsChecked = AppSettings.WarnIfFiltersActive;
3232

33-
AiEndpointTextBox.Text = UserSettings.AiEndpoint;
34-
AiModelTextBox.Text = UserSettings.AiModel;
33+
AiEndpointTextBox.Text = UserPreferences.AiEndpoint;
34+
AiModelTextBox.Text = UserPreferences.AiModel;
3535
if (AiCredentialStorage.HasApiKey())
3636
{
3737
AiApiKeyBox.Password = "placeholder";
@@ -55,8 +55,8 @@ private void SaveSettingsFromUi()
5555

5656
AppSettings.DefaultProjectExcludeFilter = ProjectExcludeFilterTextBox.Text;
5757

58-
UserSettings.AiEndpoint = AiEndpointTextBox.Text.Trim();
59-
UserSettings.AiModel = AiModelTextBox.Text.Trim();
58+
UserPreferences.AiEndpoint = AiEndpointTextBox.Text.Trim();
59+
UserPreferences.AiModel = AiModelTextBox.Text.Trim();
6060

6161
// Only update the stored key if the user actually typed something new
6262
var typedKey = AiApiKeyBox.Password;
@@ -68,9 +68,9 @@ private void SaveSettingsFromUi()
6868

6969
private void LoadDefaultSettings()
7070
{
71-
AppSettings = new ApplicationSettings();
72-
UserSettings.AiEndpoint = UserSettings.DefaultAiEndpoint;
73-
UserSettings.AiModel = UserSettings.DefaultAiModel;
71+
AppSettings = new AppSettings();
72+
UserPreferences.AiEndpoint = UserPreferences.DefaultAiEndpoint;
73+
UserPreferences.AiModel = UserPreferences.DefaultAiModel;
7474
LoadSettingsToUi();
7575
}
7676

0 commit comments

Comments
 (0)