20
$\begingroup$

I've had Mathematica crash on me a number of times, and have had to recreate notebook contents from the last save point.

I was looking for something in the preferences like an autosave frequency configuration parameter that I could use to automatically save all my notebook content at some regular frequency to minimize losses due to crashes.

I found NotebookSave[], and a NotebookAutoSave -> True attribute for CreateDocument, but both these appear to be for manipulating external notebooks. Is there a way to do this systematically and by default?

$\endgroup$
1
  • 1
    $\begingroup$ I don't think there's such an option. In any case, this answer might be relevant for your needs. What it does is it saves a copy of the current notebook (with a .backup extension) upon every cell evaluation. $\endgroup$
    – rm -rf
    Nov 2, 2012 at 5:08

9 Answers 9

16
$\begingroup$

You can set the NotebookAutoSave preference programmatically or via the Option Inspector.

To turn autosave on by default for all notebooks, you could do:

CurrentValue[$FrontEnd, "NotebookAutoSave"] = True

You can also set the option for individual notebooks by changing $FrontEnd to the appropriate notebook handle. According to the docs, NotebookAutoSave saves after every evaluation and seems to be inflexible in that regard.

$\endgroup$
1
  • 1
    $\begingroup$ Excellent. With that and checking in my notebooks to my git repository, I should have decent recoverability. I can always rollback to my last VCS commit (provided I explicitly commit after interesting changes) should I not like what has gotten autosaved, but should avoid losses due to unforeseen crashes. $\endgroup$ Nov 3, 2012 at 1:18
10
$\begingroup$

I have a Mathematica package that provides auto-backup functionality. When enabled, the current notebook--call it "blah.nb"--will be backed up to "blah.nb~" after a configurable amount of time has elapsed. I use it constantly and it has saved me from losing work many, many times. It's better than autosaving since it doesn't touch the actual notebook file: if you screw something up or something gets corrupted you don't want to overwrite your main file. :)

It's on GitHub here.

$\endgroup$
4
  • $\begingroup$ This is really nice, thanks for that! $\endgroup$
    – Gremlin
    Feb 3, 2015 at 10:34
  • $\begingroup$ Excellent. I especially like the message at the bottom. But why not use a scheduled task instead of (or in addition to) $Pre to run it? $\endgroup$
    – masterxilo
    Feb 2, 2017 at 22:32
  • $\begingroup$ Looks like NotebookFileName[] is not defined within scheduled task callbacks though... $\endgroup$
    – masterxilo
    Feb 2, 2017 at 22:38
  • 1
    $\begingroup$ I cannot understate how often this has saved me already in the past 20 days (like 10 times). You should promote it more. This is what the built-in autosave should be. $\endgroup$
    – masterxilo
    Feb 23, 2017 at 12:18
8
$\begingroup$

You could try executing

RunScheduledTask[
    EmitSound[Sound[SoundNote[]]];NotebookSave[EvaluationNotebook[]], 10];

which saves the notebook every 10 seconds and plays a note.

$\endgroup$
2
  • $\begingroup$ Mathematica system sounds really suck. That floking thing wouldnt STFU when i tried on a docked cell and didnt save my notebook but the thing didnt forget to make that annoying sound. If its a docked cell isnt there a visual cue that could be made every time the save is activated? adding a countdown timer wouldnt hurt either. $\endgroup$ Sep 16, 2020 at 9:44
  • $\begingroup$ Didn't work for me. $\endgroup$
    – WillG
    Mar 3, 2023 at 15:51
8
$\begingroup$

From Mathematica Engineers:

A simple way of doing this is to force the task into the Front End, since the Front End knows about your notebook. One technique is to use Dynamic:

Dynamic[Refresh[NotebookSave[]; DateString[], UpdateInterval -> 5]]
$\endgroup$
2
  • $\begingroup$ @Zak if you don't mind me asking by "Mathematica Engineers" do you mean an engineer employed by Wolfram or an engineer who specializes in Mathematica? Regardless I found your solution to be the best of all answers I viewed on this site. It is short, sweet, and does not perform all kinds of convoluted gymnastics to get the job done. I took your solution and edited it for flexibility: autoSave[minutes_ :10] := Dynamic[Refresh[NotebookSave[]; DateString[], UpdateInterval -> minutes 60]]; autoSave[] Could you suggest a possible way of informing user when an autoSave[] has occurred? $\endgroup$ Jun 25, 2020 at 0:01
  • $\begingroup$ Thank you, this is the only one that worked for me. I am running Mathematica 12 on Mac OS X 10.15.6. $\endgroup$
    – Virgo
    Oct 9, 2020 at 18:27
4
$\begingroup$

Motivated by the same concerns, I wrote the following code and added it to my init.m file. There are two main entries you'll want to change to use this. The global variable $SwapDirectory is where the swap files are saved (by swap file, I mean it in the VIm sense; an "extra" copy of your notebook, separate from your manually saved copy that periodically saves any new work). The swap files are organized within the swap directory in a directory structure which "mirrors" their original file locations, and have ".swp" appended to their file names. The other variable you might want to change is the number of seconds between autosaves, indicated by the "300" (corresponding to 5 minutes) near the bottom of the code below. At the appropriate times, this code will (automatically in the background) save swap files for ALL open notebooks, unless they are unmodified from their manually-saved versions (this exception makes the code more efficient, and more importantly, prevents the storage of swap files for documentation notebooks, for example).

Some things to note:
1) the Mathematica Put command seems to have trouble writing to network drives, even when offline access is enabled. Therefore, it is probably best to choose a SwapDirectory that is on your local machine.

2) Within SwapDirectory, you should create a sub-directory called "Recovery". This is where the AutoSaveSwap routine will make an initial save of any notebooks for which there is NO existing manual save location.

3) Recovering any given notebook is easy. Simply evaluate


RecoverSwap["filePath"]
where "filePath" is a string representing the filePath of the MANUALLY-SAVED copy of the file (i.e., not the file that was created by AutoSave). This will then pop up a window containing the most recent auto-saved version of the file. The manually saved version is NEVER overwritten, unless you explicitly choose to do so. Once the recovered version pops up, you can save it whereever you like, or discard it at your discretion.

4) You should probably add this code to the KERNEL version of init.m ($UserBaseDirectory/Kernel/init.m) rather than the frontend version... this way, if you quit and restart the kernel, the autosave feature will also restart. On the other hand, this means that you must evaluate at least one expression after each start or restart to begin auto-saving. Once this initial evaluation is done, you do NOT need to have evaluated a cell for it to be backed up (unlike the built-in autosave utility).

Hope this helps someone! Feel free to respond with any questions, suggestions, or requests for improvement you may have. And, if you find this post useful, upvotes would be most appeciated! Take care.


$SwapDirectory= "C:\\Users\\pacoj\\Swap Files\\"; 
SaveSwap[nb_NotebookObject]:=Module[
    {fileName, swapFileName, nbout, nbdir, nbdirout, recoveryDir},
    If[ ! SameQ[Quiet[NotebookFileName[nb]], $Failed],
    (* if the notebook is already saved to the file system *)
        fileName = Last[ FileNameSplit[ NotebookFileName[nb]] ];
        swapFileName = fileName <> ".swp";
        nbdir = Rest[FileNameSplit @ NotebookDirectory[nb]];
        nbdirout= FileNameJoin[ FileNameSplit[$SwapDirectory]~Join~nbdir]<>"\\";
        If[!DirectoryQ[nbdirout], CreateDirectory[nbdirout]];
        nbout = NotebookGet[nb];
        Put[nbout, nbdirout <> swapFileName],
        (* else, if the file has never been saved, save as untitled *)
        recoveryDir= $SwapDirectory <> "Recovery\\";
        fileName= ("WindowTitle" /. NotebookInformation[nb])<>".nb";
        NotebookSave[nb, recoveryDir <> fileName]
    ] 
]; 
RecoverSwap::noswp= "swap file 1 not found in expected location";
    RecoverSwap[nbfilename_String]:=Module[
    {fileName, swapFileName, nbin, nbdir, nbdirout},
    fileName= Last[ FileNameSplit[ nbfilename] ];
    swapFileName= fileName <> ".swp";
    nbdir= Most[ Rest[FileNameSplit @ nbfilename] ];
    nbdirout= FileNameJoin[ FileNameSplit[$SwapDirectory]~Join~nbdir]<>"\\";
    If[ FileNames[swapFileName, {nbdirout}] == {},
       Message[RecoverSwap::noswp,nbdirout <> swapFileName]; Return[],
       nbin= Get[nbdirout <> swapFileName]; NotebookPut[nbin]
    ] 
]; 
AutoSaveSwaps= CreateScheduledTask[
    SaveSwap /@ Select[Notebooks[], "ModifiedInMemory" /. NotebookInformation[#]&],
    300 
] 
StartScheduledTask[AutoSaveSwaps] 

$\endgroup$
1
  • $\begingroup$ I like this solution, but it would be better to use $PathnameSeparator instead of "\\" so that it works on Linux & MacOS without modification. $\endgroup$
    – Matt Pusey
    Aug 14, 2018 at 16:01
2
$\begingroup$

I use the following command in my init.m file to automatically save all open notebooks every SS seconds:

RunScheduledTask[Thread[NotebookSave[Drop[Notebooks[], -1]]], SS]

Notebooks[] gives us the list of all open notebooks objects, including the message console (usually invisible). Since we don't want to save the latter, we drop it from the list with Drop[..., -1]. Thread applies NotebookSave to each notebook object in the remaining list. RunScheduledTask[..., SS] runs the above operation every SS seconds.

$\endgroup$
1
  • $\begingroup$ I think this also tries to save the help window. $\endgroup$
    – masterxilo
    Feb 13, 2016 at 20:59
2
$\begingroup$

Based on grandrew's post I developed my own solution. It is published in the Wolfram Community as Auto-Save Notebooks & Recover After Crashes

It selects all edited notebooks, saves backup copies with changed names and SessionSubmit[ScheduledTask[...],...] to run periodically.

$\endgroup$
1
$\begingroup$

For anybody looking for simple solution you can add this at startup:

RunScheduledTask[
  NotebookSave /@ 
   Select[Notebooks[], 
    KeyExistsQ["Uri"]@
       NotebookInformation[#] && ("ModifiedInMemory" /. 
        NotebookInformation[#]) &], 600];

It saves every 600 seconds everything that has been saved at least once and has been modified in memory. This effectively ignores untitled windows, help, messages, etc. and saves whatever work you've been doing in every open notebook.

$\endgroup$
0
$\begingroup$

The "Dynamic" solution does not work since Dynamic objects only run if they are visible on the user's screen. If the user scrolls away from the Dynamic object, then it does not run.

This solution works:

currentNotebook = EvaluationNotebook[];
autosaveInterval = Quantity[300, "Seconds"];
autosaveTask = 
 SessionSubmit[
  ScheduledTask[
   If["ModifiedInMemory" /. NotebookInformation[currentNotebook], 
    Speak["Autosave"]; NotebookSave[currentNotebook]], 
   autosaveInterval]]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.