Skip to content

[Application] Add save-location policy and guards for new projects#172

Merged
SilverSupplier merged 2 commits into
mainfrom
feature/project-save-location-policy
May 3, 2026
Merged

[Application] Add save-location policy and guards for new projects#172
SilverSupplier merged 2 commits into
mainfrom
feature/project-save-location-policy

Conversation

@SilverSupplier
Copy link
Copy Markdown
Collaborator

@SilverSupplier SilverSupplier commented May 3, 2026

Summary

  • Pin Qt application metadata (organizationName, organizationDomain, applicationName = SafeCrowd) in main.cpp so QStandardPaths::AppDataLocation resolves to a predictable path (%APPDATA%/SafeCrowd/SafeCrowd/ on Windows).
  • NewProjectWidget suggests <Documents>/SafeCrowd Projects/<sanitized name> as the project folder while the user types; OS-forbidden characters are mapped to _, and the suggested path is disambiguated with (2), (3), … when a non-empty folder already exists. Once the user picks a folder via Browse, the suggestion stops overriding their choice.
  • The suggestion helper is string-only: it no longer creates <Documents>/SafeCrowd Projects/ on disk just because the New Project screen was opened. The directory is created lazily by saveProject when the user actually saves. Browse dialog still opens at the closest existing parent of the suggested or typed path.
  • ProjectPersistence adds validateProjectFolderLocation (drive/volume root + symlink/junction rejection) and canSaveIntoProjectFolder which additionally requires the destination folder to be non-existent, empty, or contain only SafeCrowd-managed files. saveProject calls the new guard before creating the folder or copying the layout. The deletion path is intentionally untouched in this PR.
  • docs/UI.md documents the default location, naming policy, rejection criteria, and the resolved recent-projects index path.

Related Issue

Area

  • Engine
  • Domain
  • Application
  • Docs
  • Build
  • Analysis
  • Chore

Architecture Check

  • I kept the dependency direction application -> domain -> engine.
  • I did not add Qt UI code to src/domain.
  • I did not add domain or application dependencies to src/engine.
  • I used src/ as the include root.

Verification

  • cmake --preset windows-debug
  • cmake --build --preset build-debug
  • ctest --preset test-debug
  • Not run (reason below)

safecrowd_app.exe and safecrowd_tests.exe build cleanly. ctest reports 1/1 passed locally (verified again after the mkpath-removal commit).

Manual scenarios still recommended on a clean profile:

  1. Launch with no prior config — type a project name and confirm the folder field auto-fills under <Documents>/SafeCrowd Projects/<name> without that folder being created on disk yet.
  2. Repeat with a name whose target folder already contains files — the suggestion should append (2) and so on.
  3. Pick a folder via Browse, then change the project name — the chosen folder must remain.
  4. Click Save once on any project — confirm <Documents>/SafeCrowd Projects/ and the project subfolder are created at that point.
  5. Try to save into a drive root, a junction, or a folder that already contains unrelated files — each case should be refused with a clear message.
  6. Confirm %APPDATA%/SafeCrowd/SafeCrowd/recent-projects.json is the resolved recent-list path.

Risks / Follow-up

  • TOCTOU between canSaveIntoProjectFolder and the actual write is unchanged; impact is minor on a single-user desktop and a future change can wrap the write in a lock if needed.
  • Disambiguation walks (2)(999) and falls back to the base path if exhausted, deferring to the save guard for the final error message.
  • QStandardPaths::DocumentsLocation falls back to HomeLocation if empty; on extremely restricted profiles where both are unavailable, the auto-suggestion is skipped and the Browse dialog uses the OS default — the save guard still applies.
  • textChanged fires suggestProjectFolder on every keystroke, which scans up to 999 candidate folders. Cheap on a local SSD, but a future debounce could help on slow/network paths.
  • sanitizeFolderName does not catch Windows reserved names (CON, PRN, AUX, NUL, COM1...). canSaveIntoProjectFolder still rejects with a clear error, so this is a UX-message gap rather than a correctness gap.
  • After both this PR and [Application] Harden recent-project deletion flow #167 land, a small follow-up can refactor canDeleteProjectFolder to share validateProjectFolderLocation so the save and delete paths use one definition of "safe project folder location".

@SilverSupplier SilverSupplier requested a review from learncold as a code owner May 3, 2026 11:27
Define a default project save location and validate the destination
folder at save time. Pin Qt application metadata so the recent-projects
index resolves to a stable location.

- main.cpp sets QApplication organization name, organization domain,
  and application name to "SafeCrowd" so QStandardPaths::AppDataLocation
  resolves to a predictable path on every host.
- NewProjectWidget suggests
  "<Documents>/SafeCrowd Projects/<sanitized project name>" while the
  user types, sanitizing OS-forbidden characters and disambiguating
  against existing non-empty folders with a numeric suffix. Once the
  user picks a folder via Browse the suggestion stops overriding their
  choice. The Browse dialog opens at the closest existing parent of
  the current input, defaulting to the projects root.
- ProjectPersistence adds validateProjectFolderLocation (drive/volume
  root + symlink/junction rejection) and canSaveIntoProjectFolder
  which additionally requires the destination folder to be
  non-existent, empty, or contain only SafeCrowd-managed files.
  saveProject calls the new guard before creating the folder or
  copying the layout file. The deletion path is unchanged.
- docs/UI.md documents the default location, naming policy, rejection
  criteria, and the resolved recent-projects index path.
The new-project suggestion helper used to materialize the default
projects root on every keystroke, leaving an empty folder behind even
when the user never saved a project. The directory is now created
lazily by saveProject only when the user actually saves.

- defaultProjectsRoot is now string-only: it no longer calls
  QDir().mkpath, so opening the New Project screen and typing in the
  project-name field has no filesystem side effect.
- The Browse handler's cdUp walk-up is unified so it runs whenever
  startDir is non-empty, regardless of whether the path came from
  folderPathEdit_ or from the (possibly non-existent) suggested root.
  QFileDialog still opens at a sensible existing parent.
@SilverSupplier SilverSupplier force-pushed the feature/project-save-location-policy branch from 3ed70a7 to 24bef1c Compare May 3, 2026 17:28
@SilverSupplier SilverSupplier merged commit 1904c73 into main May 3, 2026
2 checks passed
@SilverSupplier SilverSupplier deleted the feature/project-save-location-policy branch May 3, 2026 17:29
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.

Task-프로젝트 저장 위치 정책 및 가드 정비

1 participant