Implement atomic save

Ping @fperez, this should avoid issues with corrupted/lost notebooks when the disk is full, though I haven't worked out how to test it just yet.

Closes gh-6254
I think @jdfreder meant raise another exception wrapping the original one. I don't think that's necessary - it's written as a context manager, so if the code inside that with block throws an error, you know the write was aborted.

Exceptions actually have a `__context__` attribute pointing to the previous exception if you raise from inside an except block, and you can give them a `__cause__` by doing `raise blah from e`. Both of those things are new In Python 3, though.