Best strategy to save documents on app shutdown/quit?

To let the user saves documents when my app is shutdowned i guess i could use something like FileChooser::browseForFileToSave with JUCE_MODAL_LOOPS_PERMITTED enabled. To avoid the modal loop i wanted to use/evaluate FileBasedDocument but it seems to not use native windows on Linux. What’s your strategy? Is modal loops really evil in that case? If yes how do you suspend/delay the shutdown to wait for the user response?

1 Like

Maybe do something in the destructor of a struct that is DeletedAtShutdown?

1 Like

Thanks for your help.
What’s make hard is that the core and the GUI are two threads that communicate with messages.
I don’t want to shutdown the core engine before to let it handle the saves.
At lunch i had an idea. :grinning:
Put the documents in a release queue with async dialog called.
The dialog callback triggers the save and the deletion.
At systemRequestedQuit ask for closing all documents once and reschedule the request until the release queue is empty.
Need a try.

I wonder if the normal event queue is actually still running once we are in the systemRequestedQuit callback. It seems to me that a modal loop is the only viable thing in that case, but that’s something to try out.

1 Like

Perhaps something to consider is if saving documents on quit is a good idea to begin with. As a user, I nowdays expect apps to constantly save everything in the background, so that a crash doesn’t lead to lost information.

Also, it seems that on iOS the destructors are not always called on quit, something to keep in mind if you want to release on that platform:

1 Like

Good catch, I might try before to test that way.

In general yes, but as I’m revamping Pure Data i don’t think that in that case it is expected by users.
The original patch must stay untouched until the user decides to. You don’t want to get a broken version. And as i need to keep the original file format as far as possible, i can not really go with fancy approach (i.e. with history/revision embedded).

You can still do that if you constantly save a temporary version internally. The user can then overwrite the original patch at any moment, but now it doesn’t have to be when quitting the app.

Pure Data (which I love) is precisely one of those programs which has often caused me hours of lost work when it has crashed. It would be great if your revamped version wouldn’t have this limitation!

I’m agree with you. I could (i will probably in the future) implement a temporary file strategy to avoid lost with crashes. But at the same time it doesn’t solve my problem ; that is giving the user the choice to save (or not) at quit (if not already done before). IMHO many users expect that behaviour. The app can not overwrite the file without the explicit agreement of the user.

There is a class in JUCE for that purpose: TemporaryFile.
It writes in a temp file and moves it at the end to the destination filename.

Highly recommended!

That in addition to auto save if you want.

2 Likes

That’s a handy class/tool, but sadly all the core engine is write in C (build by C++ compiler).
The engine can run without JUCE/GUI at all.
Moreover it must be buildable by C or C++ compiler.
That makes the GUI design a bit more fun. :laughing:

I think an automatic save strategy to an internal file would indeed solve your problem.

If there are unsaved changes and I quit the app (or the app crashes) but these unsaved changes are still there when I reopen the app, then all is good. I can save the changes to my file or discard them at any moment. The only difference is that I’m not forced to decide if I want to save the changes to file when closing the app. Closing the app then just feels like closing a window.

When creating a new document, then the program would ask the user if the changes should be saved or discarded. But this would not be necessary when quitting.

If I understand you correctly, you do not want an autosave feature like in Final Cut X or Xcode, where the user’s file is constantly being saved by the program. But if you automatically save to an internal file instead, the user retains control over his file. The app does not overwrite the file without the explicit agreement of the user. The best of both worlds!

1 Like

Thanks for the explanations. Now i understand what you mean. :wink:

1 Like

I like and don’t like that approach at the same time.
Having always the last state at hand regardless if you finished the program orderly or by crash/power failure etc. is a great feature.

But when I archive my project, I should know if it is actually the last state I worked on.

So there is a case for both worlds.

1 Like

Personally i don’t like that. That’s something that always disturbe me with “Sublime Text” for instance.
What you have on screen is not what you have on disk when you open it.
But the idea to auto save a temporary copy somewhere (to be able to fetch the last state if crashed) can be a nice feature. But the software is not supposed to crash! :grinning_face_with_smiling_eyes:

Even the perfect software is doomed if you accidentally pull the power, the battery fails, etc.

1 Like

For now (after a quick test) it seems that my approach works fine.

template <class T> void performOrWaitAllRequestsDone (T f) const
{
    if (spaghettis::Spaghettis()->isAllRequestsDone()) { f(); }
    else {
        juce::Timer::callAfterDelay (500.0, [this, f = f]() { performOrWaitAllRequestsDone<T> (f); });
    }
}

void systemRequestedQuit() override
{
    performOrWaitAllRequestsDone ([]() { DBG ("!"); });
    
    spaghettis::Spaghettis()->closeAllPatches();
    
    performOrWaitAllRequestsDone ([]() { quit(); });
}

See also: