in vsintegration/src/FSharp.ProjectSystem.Base/Project/Automation/OAProject.cs [444:565]
private void DoSave(bool isCalledFromSaveAs, string fileName)
{
UIThread.DoOnUIThread(delegate() {
if (fileName == null)
{
throw new ArgumentNullException("fileName");
}
if (this.project == null || this.project.Site == null || this.project.IsClosed)
{
throw new InvalidOperationException();
}
IVsExtensibility3 extensibility = this.project.Site.GetService(typeof(IVsExtensibility)) as IVsExtensibility3;
if (extensibility == null)
{
throw new InvalidOperationException();
}
extensibility.EnterAutomationFunction();
try
{
// If an empty file name is passed in for Save then make the file name the project name.
if (!isCalledFromSaveAs && string.IsNullOrEmpty(fileName))
{
// Use the solution service to save the project file. Note that we have to use the service
// so that all the shell's elements are aware that we are inside a save operation and
// all the file change listenters registered by the shell are suspended.
// Get the cookie of the project file from the RTD.
IVsRunningDocumentTable rdt = this.project.Site.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (null == rdt)
{
throw new InvalidOperationException();
}
IVsHierarchy hier;
uint itemid;
IntPtr unkData = IntPtr.Zero;
uint cookie;
try
{
ErrorHandler.ThrowOnFailure(rdt.FindAndLockDocument((uint)_VSRDTFLAGS.RDT_NoLock, this.project.Url, out hier,
out itemid, out unkData, out cookie));
} finally {
if (IntPtr.Zero != unkData)
{
Marshal.Release(unkData);
}
}
// Verify that we have a cookie.
if (0 == cookie)
{
// This should never happen because if the project is open, then it must be in the RDT.
throw new InvalidOperationException();
}
// Get the IVsHierarchy for the project.
IVsHierarchy prjHierarchy = project.InteropSafeIVsHierarchy;
// Now get the soulution.
IVsSolution solution = this.project.Site.GetService(typeof(SVsSolution)) as IVsSolution;
// Verify that we have both solution and hierarchy.
if ((null == prjHierarchy) || (null == solution))
{
throw new InvalidOperationException();
}
ErrorHandler.ThrowOnFailure(solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_SaveIfDirty, prjHierarchy, cookie));
}
else
{
// We need to make some checks before we can call the save method on the project node.
// This is mainly because it is now us and not the caller like in case of SaveAs or Save that should validate the file name.
// The IPersistFileFormat.Save method only does a validation that is necesseray to be performed. Example: in case of Save As the
// file name itself is not validated only the whole path. (thus a file name like file\file is accepted, since as a path is valid)
// 1. The file name has to be valid.
string fullPath = fileName;
try
{
if (!Path.IsPathRooted(fileName))
{
fullPath = Path.Combine(this.project.ProjectFolder, fileName);
}
}
// We want to be consistent in the error message and exception we throw. ArgumentException on Path.IsRooted shoud become InvalidOperationException.
catch (ArgumentException)
{
throw new InvalidOperationException(SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture));
}
// It might be redundant but we validate the file and the full path of the file being valid. The SaveAs would also validate the path.
// If we decide that this is performance critical then this should be refactored.
Utilities.ValidateFileName(this.project.Site, fullPath);
if (!isCalledFromSaveAs)
{
// 2. The file name has to be the same
if (!NativeMethods.IsSamePath(fullPath, this.project.Url))
{
throw new InvalidOperationException();
}
ErrorHandler.ThrowOnFailure(this.project.Save(fullPath, 1, 0));
}
else
{
ErrorHandler.ThrowOnFailure(this.project.Save(fullPath, 0, 0));
}
}
}
finally
{
extensibility.ExitAutomationFunction();
}
});
}