in src/Editor/Text/Impl/TextModel/FileUtilities.cs [18:165]
public static void SaveSnapshot(ITextSnapshot snapshot,
FileMode fileMode,
Encoding encoding,
string filePath)
{
Debug.Assert((fileMode == FileMode.Create) || (fileMode == FileMode.CreateNew));
//Save the contents of the text buffer to disk.
string temporaryFilePath = null;
try
{
FileStream originalFileStream = null;
FileStream temporaryFileStream = FileUtilities.CreateFileStream(filePath, fileMode, out temporaryFilePath, out originalFileStream);
if (originalFileStream == null)
{
//The "normal" scenario: save the snapshot directly to disk. Either:
// there are no hard links to the target file so we can write the snapshot to the temporary and use File.Replace.
// we're creating a new file (in which case, temporaryFileStream is a misnomer: it is the stream for the file we are creating).
try
{
using (StreamWriter streamWriter = new StreamWriter(temporaryFileStream, encoding))
{
snapshot.Write(streamWriter);
}
}
finally
{
//This is somewhat redundant: disposing of streamWriter had the side-effect of disposing of temporaryFileStream
temporaryFileStream.Dispose();
temporaryFileStream = null;
}
if (temporaryFilePath != null)
{
//We were saving to the original file and already have a copy of the file on disk.
int remainingAttempts = 3;
do
{
try
{
//Replace the contents of filePath with the contents of the temporary using File.Replace to
//preserve the various attributes of the original file.
File.Replace(temporaryFilePath, filePath, null, true);
temporaryFilePath = null;
return;
}
catch (FileNotFoundException)
{
// The target file doesn't exist (someone deleted it after we detected it earlier).
// This is an acceptable condition so don't throw.
File.Move(temporaryFilePath, filePath);
temporaryFilePath = null;
return;
}
catch (IOException)
{
//There was some other exception when trying to replace the contents of the file
//(probably because some other process had the file locked).
//Wait a few ms and try again.
System.Threading.Thread.Sleep(5);
}
}
while (--remainingAttempts > 0);
//We're giving up on replacing the file. Try overwriting it directly (this is essentially the old Dev11 behavior).
//Do not try approach we are using for hard links (copying the original & restoring it if there is a failure) since
//getting here implies something strange is going on with the file system (Git or the like locking files) so we
//want the simplest possible fallback.
//Failing here causes the exception to be passed to the calling code.
using (FileStream stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
using (StreamWriter streamWriter = new StreamWriter(stream, encoding))
{
snapshot.Write(streamWriter);
}
}
}
}
else
{
//filePath has hard links so we need to use a different approach to save the file:
// copy the original file to the temporary
// write directly to the original
// restore the original in the event of errors (which could be encoding errors and not disk issues) if there's a problem.
try
{
// Copy the contents of the original file to the temporary.
originalFileStream.CopyTo(temporaryFileStream);
//We've got a clean copy, try writing the snapshot directly to the original file
try
{
originalFileStream.Seek(0, SeekOrigin.Begin);
originalFileStream.SetLength(0);
//Make sure the StreamWriter is flagged leaveOpen == true. Otherwise disposing of the StreamWriter will dispose of originalFileStream and we need to
//leave originalFileStream open so we can use it to restore the original from the temporary copy we made.
using (var streamWriter = new StreamWriter(originalFileStream, encoding, bufferSize: 1024, leaveOpen: true)) //1024 == the default buffer size for a StreamWriter.
{
snapshot.Write(streamWriter);
}
}
catch
{
//Restore the original from the temporary copy we made (but rethrow the original exception since we didn't save the file).
temporaryFileStream.Seek(0, SeekOrigin.Begin);
originalFileStream.Seek(0, SeekOrigin.Begin);
originalFileStream.SetLength(0);
temporaryFileStream.CopyTo(originalFileStream);
throw;
}
}
finally
{
originalFileStream.Dispose();
originalFileStream = null;
temporaryFileStream.Dispose();
temporaryFileStream = null;
}
}
}
finally
{
if (temporaryFilePath != null)
{
try
{
//We do not need the temporary any longer.
if (File.Exists(temporaryFilePath))
{
File.Delete(temporaryFilePath);
}
}
catch
{
//Failing to clean up the temporary is an ignorable exception.
}
}
}
}