Skip to content

feat(presets): save to Ask every time (#521) - #572

Open
cre8ive-marketing wants to merge 4 commits into
KnifeLemon:mainfrom
cre8ive-marketing:feat/521-ask-every-time
Open

cre8ive-marketing wants to merge 4 commits into
KnifeLemon:mainfrom
cre8ive-marketing:feat/521-ask-every-time

Conversation

@cre8ive-marketing

Copy link
Copy Markdown
Collaborator

Closes #521, as approved there: the dialog starts in the source's folder and asks once per batch.

What it does

Settings → Presets → Save to → Ask every time (a new OutputLocation.Ask). Converting with such a preset from the donut first asks where to save:

  • One result (a single file, or a merge / joined clip of several): a Save as dialog in the source's folder, with the name the preset would give (pattern, renames, target extension). The result is saved under exactly the name chosen; the dialog has already asked about replacing a file there.
  • Several results: a folder dialog, once for the whole batch, starting in the first source's folder. Every result goes there under the preset's own names; an existing file there is numbered as usual, not replaced.
  • Cancel converts nothing.
  • The donut closes for the dialog, and the job shows in the toast. Retrying a failed file uses the place already chosen; it doesn't ask again.
  • Where nothing can ask (watch folders, the command line, other tools' single conversions), the result is saved next to the source.
  • Not in this PR: files dragged from a browser keep saving into the download folder, as "next to the source" does today.

The default stays as it is; only presets set to this option ask. No settings or schema change: an older Filee reading the preset falls back to its default location. Strings (presets.location.Ask, ask.save_title, ask.folder_title) are in all 16 languages.

Tests

  • New AskedLocationTests (Core):
    • the dialog suggests the name the preset would give, in the target's or the source's extension;
    • where nothing asks, the result is saved next to the source;
    • one result is saved exactly as chosen, even over an existing file, without the pattern or renames on top, and the preset itself keeps asking;
    • several results go into the chosen folder under their own names, and an existing file is numbered, not replaced.
  • LocalizationTests and the preset editor tests pass with the new choice in the list.
  • The dialog itself is the system's, so it isn't run headless; the logic around it is in AskedLocation.
  • dotnet format --verify-no-changes is clean.
  • Full dotnet test: Platform 81, Core 606, CLI 27, Setup 176, Engines 1821 and App 1344 pass, none fail.

A preset can ask where to save: a save dialog that starts in the source's
folder, once per batch. One result (a single file, or a merge) is saved
under the name chosen; several go into the chosen folder under their own
names. Watch folders and the command line can't ask: they save next to the
source. Files from a browser keep saving into the download folder.
@cre8ive-marketing cre8ive-marketing changed the title feat(presets): save to \"Ask every time\" (#521) feat(presets): save to Ask every time (#521) Oct 25, 2026
@github-actions github-actions Bot added the feature New features label Oct 25, 2026

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks @cre8ive-marketing, this follows what we agreed in #521: the dialog starts in the source's folder, a batch asks once, Cancel starts nothing, and the library preset stays untouched (the answered copy is what runs, so a retry keeps the chosen place). Keeping the decision logic in AskedLocation with tests is good, and nothing outside the donut can wait on a dialog: the CLI resolves Ask next to the source, and watch folders always write into their own output folder (WatchRule.Apply sets the location), so neither can hang.

A few things to fix:

  1. Overwrite reaches files the dialog never asked about: src/Filee.Core/Presets/AskedLocation.cs:39. The whole job runs with ConflictPolicy.Overwrite, but the dialog confirmed one exact path:

    • one source that becomes several files (a PDF split into pages, PDF → JPG per page, frames, Extract): holiday_p1.jpg, holiday_p2.jpg, … silently replace existing files;
    • a typed extension other than the target's (holiday.jpeg or holiday.png with a JPG preset): the result is holiday.jpg, not the confirmed name, and an existing holiday.jpg is replaced without asking;
    • "Save as ZIP" (or Settings › General's ZIP for geo results): the dialog suggests .jpg (Extension, line 24) but the result is holiday.zip, replaced silently.

    I confirmed all three with OutputPathResolver.Resolve on the asked rule. Suggested fix: keep the preset's own Conflict and let only the exact confirmed path be replaced (e.g. a runtime-only [JsonIgnore] path on OutputRule that Resolve treats as free); ask with store.Settings.WithDefaultOutput(preset) and return zip from Extension when the result is zipped. A JobQueue test with an existing name_p1 / name.zip would cover it.

  2. An older Filee resets every preset when it reads "location": "Ask": src/Filee.Core/Presets/OutputRule.cs:40. Location goes through the context-wide UseStringEnumConverter (FileeJsonContext), not LenientEnumJsonConverter, so 2.1.0 throws JsonException on the unknown name; UserDataStore.Read then treats presets.json as broken, loads the factory presets, and the next save overwrites the user's. The same applies to history.json (a HistoryEntry keeps its preset, and runs that didn't ask, such as the CLI or browser drops, store Ask) and to an exported bundle imported into 2.1.0. So "an older Filee falls back to its default location" in the description doesn't hold. Fix: leave Location as it is and keep the choice in a separate bool (e.g. AskEveryTime: older versions skip unknown properties and use Location), and put [JsonConverter(typeof(LenientEnumJsonConverter<OutputLocation>))] on Location for future values. No schema bump is needed (a missing bool reads as false); please don't take 47, other open PRs already use it.

  3. Several sources, one result, but the folder dialog: src/Filee.App/Services/RadialController.cs:354. oneResult: sources.Count == 1 sends "Combine into one archive" (PacksIntoOneArchive) and multi-page TIFF to the folder picker, though they make one file (JobQueue uses its one-file allocator for both). Treat those as one result too, as the description says.

  4. "Replace the original" throws the chosen place away: src/Filee.Core/Presets/AskedLocation.cs:46 (InFolder). With ReplaceOriginal on (a same-format preset such as compress), JobQueue.ReplaceOriginal moves the result back over the source after the user picked where to save. Set copy.Output.ReplaceOriginal = false there, as WatchRule.Apply does.

One question: did you try it on macOS? The dialog's owner is a 1×1 transparent, undecorated window; if the save panel attaches to it as a sheet, it may show in an odd place or behind other apps.

German (#541) was merged after this PR was opened, so de.json is missing the three new keys; we'll add those on our side. Once the four points are in and CI is green, this is good to merge.

…ne-file batches

From the review of KnifeLemon#572:
- The job keeps the preset's own conflict policy; only the exact path the save dialog confirmed may replace a file (OutputRule.ConfirmedPath, runtime only). Pages, a typed extension other than the result's, and ZIPs get numbered as usual. The dialog is asked with Settings › General applied, and suggests .zip for a ZIP.
- "Ask every time" is OutputRule.AskEveryTime, not a Location value, so an older Filee keeps the preset and uses its Location. Location reads an unknown name as Default (LenientEnumJsonConverter).
- Several sources that make one file (merged PDF, multi-page TIFF, one archive, joins) are asked for by name (AskedLocation.MakesOneResult).
- The chosen place turns "Replace the original" off for that run.
@cre8ive-marketing

Copy link
Copy Markdown
Collaborator Author

@KnifeLemon, thanks for checking each case with Resolve. All four points are in, pushed as 4e26f8f, with main merged in:

  1. Only the confirmed file is replaced: the job keeps the preset's own Conflict. OutputRule.ConfirmedPath ([JsonIgnore], runtime only) is the one path Resolve treats as free. Pages (holiday_p1), a typed extension other than the result's, and a ZIP are numbered as usual.
    • The dialog is asked with store.Settings.WithDefaultOutput(preset), and Extension returns zip when the result is zipped.
    • The tests include a JobQueue run with an existing holiday_p1.png, which is kept.
  2. Older Filee: "Ask every time" is now OutputRule.AskEveryTime, a bool, and Location keeps what it was, so 2.1.0 skips the bool and uses the location. OutputLocation has no new value, and Location has LenientEnumJsonConverter. No schema bump.
    • The editor shows it as the last "Save to" choice, which has no location value.
    • Tests: a saved preset contains no "Ask", and a library with an unknown location name loads with Default instead of being replaced by the factory presets.
  3. One result from several sources: AskedLocation.MakesOneResult covers a merged PDF, a multi-page TIFF, one archive (PacksIntoOneArchive) and joins, so those get the name dialog.
  4. Replace the original: the answered copy sets ReplaceOriginal = false.

macOS: no, I haven't tried it there; we don't have a Mac. If the panel attaches to the 1×1 host as a sheet, it could show in an odd place. An option is to use the main window as the owner when it's visible, and the host only when Filee is in the tray. Could someone with a Mac (for example the contributor from #551) give it a quick try?

I added the three German strings already (7e3d2a1), so there's nothing left to add on your side.

Full dotnet test: Platform 81, Core 611, CLI 30, Setup 181, Engines 1822 and App 1369 pass, none fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: 转换后的文件保存

2 participants