Skip to content

feat(#8): Project configuration load/save#49

Open
ModerRAS wants to merge 1 commit intomasterfrom
feature/issue-8-project-config
Open

feat(#8): Project configuration load/save#49
ModerRAS wants to merge 1 commit intomasterfrom
feature/issue-8-project-config

Conversation

@ModerRAS
Copy link
Copy Markdown
Contributor

Summary

  • Create ProjectSettings and WindowSettings models
  • Implement ProjectSettingsService with async support
  • Support saving/loading project settings to JSON
  • Track recent files, open tabs, and window state
  • Add comprehensive unit tests
  • Register service in DI container

Changes

  • BannerlordModEditor.Common/Services/ProjectSettingsService.cs - Implementation
  • BannerlordModEditor.Common.Tests/Services/ProjectSettingsServiceTests.cs - Unit tests
  • BannerlordModEditor.UI/App.axaml.cs - DI registration

Closes #8

- Create ProjectSettings and WindowSettings models
- Implement ProjectSettingsService with async support
- Support saving/loading project settings to JSON
- Track recent files, open tabs, and window state
- Add comprehensive unit tests
- Register service in DI container

Closes #8
Copilot AI review requested due to automatic review settings March 20, 2026 01:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a JSON-backed project configuration system with async load/save support and wires it into the UI DI container, along with unit tests.

Changes:

  • Introduces ProjectSettings / WindowSettings models and IProjectSettingsService + implementation.
  • Adds JSON persistence (sync + async) for project settings (recent files, tabs, window state).
  • Registers new common services in the UI DI container and adds unit tests.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.

File Description
BannerlordModEditor.UI/App.axaml.cs Registers new services (scanner/detector/settings) in DI.
BannerlordModEditor.Common/Services/ProjectSettingsService.cs Implements models + JSON load/save service (sync/async).
BannerlordModEditor.Common.Tests/Services/ProjectSettingsServiceTests.cs Adds xUnit coverage for default/path/load/save scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +50 to +53
public async Task<ProjectSettings> LoadAsync(string projectPath)
{
return await Task.Run(() => Load(projectPath));
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoadAsync/SaveAsync are implemented via Task.Run, which offloads synchronous disk I/O to the thread pool rather than using true async file I/O. This can reduce scalability and responsiveness under load. Prefer File.ReadAllTextAsync/File.WriteAllTextAsync (and async JSON serialize/deserialize where applicable) so the async API is genuinely non-blocking.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +77
public async Task SaveAsync(ProjectSettings settings, string projectPath)
{
await Task.Run(() => Save(settings, projectPath));
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoadAsync/SaveAsync are implemented via Task.Run, which offloads synchronous disk I/O to the thread pool rather than using true async file I/O. This can reduce scalability and responsiveness under load. Prefer File.ReadAllTextAsync/File.WriteAllTextAsync (and async JSON serialize/deserialize where applicable) so the async API is genuinely non-blocking.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +71
catch
{
return GetDefault(projectPath);
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The blanket catch { return GetDefault(...) } will silently hide parsing errors and filesystem failures, making issues hard to diagnose (and potentially masking real misconfigurations). Consider narrowing to expected exceptions (e.g., JsonException, IOException) and at minimum emitting a log/trace entry; for unexpected exceptions, consider rethrowing so failures aren’t silently ignored.

Suggested change
catch
{
return GetDefault(projectPath);
}
catch (JsonException ex)
{
Console.Error.WriteLine($"Failed to parse project settings file '{settingsPath}': {ex}");
return GetDefault(projectPath);
}
catch (IOException ex)
{
Console.Error.WriteLine($"I/O error while reading project settings file '{settingsPath}': {ex}");
return GetDefault(projectPath);
}

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +91
settings.ProjectPath = projectPath;
settings.LastOpened = DateTime.Now;
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DateTime.Now is local-time dependent and makes values harder to compare across machines/timezones; it also tends to complicate deterministic tests. Prefer DateTime.UtcNow or DateTimeOffset.UtcNow for persisted timestamps. Also, mutating settings.ProjectPath and settings.LastOpened inside Save is a side effect that may surprise callers; if this is required behavior, consider documenting it on the interface/method.

Copilot uses AI. Check for mistakes.
settings.LastOpened = DateTime.Now;

var json = JsonSerializer.Serialize(settings, JsonOptions);
File.WriteAllText(settingsPath, json);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing directly to the final settings file risks leaving a partially written/corrupted JSON file if the process crashes mid-write or the disk fills up. Prefer an atomic write pattern (write to a temp file in the same directory, flush, then replace/move over the target) to improve reliability.

Suggested change
File.WriteAllText(settingsPath, json);
var tempSettingsPath = settingsPath + ".tmp";
File.WriteAllText(tempSettingsPath, json);
File.Move(tempSettingsPath, settingsPath, true);

Copilot uses AI. Check for mistakes.
{
var result = _service.GetSettingsFilePath("/test/project");

Assert.Equal("/test/project/.bannerlordmodeditor.json", result);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion hard-codes / path separators and will fail on Windows where Path.Combine uses \. Build the expected value using Path.Combine("/test/project", ".bannerlordmodeditor.json") (or construct the test input using Path.Combine too) so the test is cross-platform.

Suggested change
Assert.Equal("/test/project/.bannerlordmodeditor.json", result);
var expectedPath = Path.Combine("/test/project", ".bannerlordmodeditor.json");
Assert.Equal(expectedPath, result);

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +29
public class ProjectSettings
{
public string ProjectName { get; set; } = string.Empty;
public string ProjectPath { get; set; } = string.Empty;
public string GameVersion { get; set; } = "Latest";
public string LastOpenedFile { get; set; } = string.Empty;
public DateTime LastOpened { get; set; }
public Dictionary<string, string> CustomSettings { get; set; } = new();
public List<string> RecentFiles { get; set; } = new();
public List<string> OpenTabs { get; set; } = new();
public WindowSettings Window { get; set; } = new();
}

public class WindowSettings
{
public double Width { get; set; } = 1200;
public double Height { get; set; } = 800;
public double X { get; set; }
public double Y { get; set; }
public bool IsMaximized { get; set; }
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProjectSettings/WindowSettings models and the service/interface are all declared in the same file. Since these are likely to be consumed independently (models by UI, service by DI/tests), splitting them into separate files (e.g., Models/ProjectSettings.cs, Models/WindowSettings.cs, Services/IProjectSettingsService.cs) will make the codebase easier to navigate and evolve.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 添加项目的加载和保存配置功能

2 participants