[Application] Add save-location policy and guards for new projects#172
Merged
Conversation
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.
3ed70a7 to
24bef1c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
organizationName,organizationDomain,applicationName=SafeCrowd) inmain.cppsoQStandardPaths::AppDataLocationresolves to a predictable path (%APPDATA%/SafeCrowd/SafeCrowd/on Windows).NewProjectWidgetsuggests<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.<Documents>/SafeCrowd Projects/on disk just because the New Project screen was opened. The directory is created lazily bysaveProjectwhen the user actually saves. Browse dialog still opens at the closest existing parent of the suggested or typed path.ProjectPersistenceaddsvalidateProjectFolderLocation(drive/volume root + symlink/junction rejection) andcanSaveIntoProjectFolderwhich additionally requires the destination folder to be non-existent, empty, or contain only SafeCrowd-managed files.saveProjectcalls the new guard before creating the folder or copying the layout. The deletion path is intentionally untouched in this PR.docs/UI.mddocuments the default location, naming policy, rejection criteria, and the resolved recent-projects index path.Related Issue
Area
Architecture Check
application -> domain -> engine.src/domain.domainorapplicationdependencies tosrc/engine.src/as the include root.Verification
cmake --preset windows-debugcmake --build --preset build-debugctest --preset test-debugsafecrowd_app.exeandsafecrowd_tests.exebuild cleanly.ctestreports 1/1 passed locally (verified again after the mkpath-removal commit).Manual scenarios still recommended on a clean profile:
<Documents>/SafeCrowd Projects/<name>without that folder being created on disk yet.(2)and so on.<Documents>/SafeCrowd Projects/and the project subfolder are created at that point.%APPDATA%/SafeCrowd/SafeCrowd/recent-projects.jsonis the resolved recent-list path.Risks / Follow-up
canSaveIntoProjectFolderand 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.(2)–(999)and falls back to the base path if exhausted, deferring to the save guard for the final error message.QStandardPaths::DocumentsLocationfalls back toHomeLocationif 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.textChangedfiressuggestProjectFolderon every keystroke, which scans up to 999 candidate folders. Cheap on a local SSD, but a future debounce could help on slow/network paths.sanitizeFolderNamedoes not catch Windows reserved names (CON,PRN,AUX,NUL,COM1...).canSaveIntoProjectFolderstill rejects with a clear error, so this is a UX-message gap rather than a correctness gap.canDeleteProjectFolderto sharevalidateProjectFolderLocationso the save and delete paths use one definition of "safe project folder location".