/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * ***************************************************************************/ using System; using System.Globalization; using System.Runtime.InteropServices; using EnvDTE; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.Project.Automation { [ComVisible(true)] public class OAProject : EnvDTE.Project, EnvDTE.ISupportVSProperties { #region fields private ProjectNode project; EnvDTE.ConfigurationManager configurationManager; #endregion #region properties public object Project { get { return this.project; } } public ProjectNode ProjectNode { get { return this.project; } } #endregion #region ctor public OAProject(ProjectNode project) { this.project = project; } #endregion #region EnvDTE.Project /// /// Gets or sets the name of the object. /// public virtual string Name { get { return project.Caption; } set { CheckProjectIsValid(); using (AutomationScope scope = new AutomationScope(this.project.Site)) { UIThread.Instance.RunSync(() => { project.SetEditLabel(value); }); } } } /// /// Microsoft public Use Only. Gets the file name of the project. /// public virtual string FileName { get { return project.ProjectFile; } } /// /// Microsoft public Use Only. Specfies if the project is dirty. /// public virtual bool IsDirty { get { int dirty; ErrorHandler.ThrowOnFailure(project.IsDirty(out dirty)); return dirty != 0; } set { CheckProjectIsValid(); using (AutomationScope scope = new AutomationScope(this.project.Site)) { UIThread.Instance.RunSync(() => { project.SetProjectFileDirty(value); }); } } } public void CheckProjectIsValid() { if (this.project == null || this.project.Site == null || this.project.IsClosed) { throw new InvalidOperationException(); } } /// /// Gets the Projects collection containing the Project object supporting this property. /// public virtual EnvDTE.Projects Collection { get { return null; } } /// /// Gets the top-level extensibility object. /// public virtual EnvDTE.DTE DTE { get { return (EnvDTE.DTE)this.project.Site.GetService(typeof(EnvDTE.DTE)); } } /// /// Gets a GUID string indicating the kind or type of the object. /// public virtual string Kind { get { return project.ProjectGuid.ToString("B"); } } /// /// Gets a ProjectItems collection for the Project object. /// public virtual EnvDTE.ProjectItems ProjectItems { get { return new OAProjectItems(this, project); } } /// /// Gets a collection of all properties that pertain to the Project object. /// public virtual EnvDTE.Properties Properties { get { return new OAProperties(this.project.NodeProperties); } } /// /// Returns the name of project as a relative path from the directory containing the solution file to the project file /// /// Unique name if project is in a valid state. Otherwise null public virtual string UniqueName { get { if (this.project == null || this.project.IsClosed) { return null; } else { // Get Solution service IVsSolution solution = this.project.GetService(typeof(IVsSolution)) as IVsSolution; VsUtilities.CheckNotNull(solution); // Ask solution for unique name of project string uniqueName; ErrorHandler.ThrowOnFailure( solution.GetUniqueNameOfProject( project.GetOuterInterface(), out uniqueName ) ); return uniqueName; } } } /// /// Gets an interface or object that can be accessed by name at run time. /// public virtual object Object { get { return this.project.Object; } } /// /// Gets the requested Extender object if it is available for this object. /// /// The name of the extender object. /// An Extender object. public virtual object get_Extender(string name) { VsUtilities.ArgumentNotNull("name", name); return DTE.ObjectExtenders.GetExtender(project.NodeProperties.ExtenderCATID.ToUpper(), name, project.NodeProperties); } /// /// Gets a list of available Extenders for the object. /// public virtual object ExtenderNames { get { return DTE.ObjectExtenders.GetExtenderNames(project.NodeProperties.ExtenderCATID.ToUpper(), project.NodeProperties); } } /// /// Gets the Extender category ID (CATID) for the object. /// public virtual string ExtenderCATID { get { return project.NodeProperties.ExtenderCATID; } } /// /// Gets the full path and name of the Project object's file. /// public virtual string FullName { get { string filename; uint format; ErrorHandler.ThrowOnFailure(project.GetCurFile(out filename, out format)); return filename; } } /// /// Gets or sets a value indicatingwhether the object has not been modified since last being saved or opened. /// public virtual bool Saved { get { return !this.IsDirty; } set { IsDirty = !value; } } /// /// Gets the ConfigurationManager object for this Project . /// public virtual EnvDTE.ConfigurationManager ConfigurationManager { get { if (this.configurationManager == null) { IVsExtensibility3 extensibility = this.project.Site.GetService(typeof(IVsExtensibility)) as IVsExtensibility3; VsUtilities.CheckNotNull(extensibility); object configurationManagerAsObject; ErrorHandler.ThrowOnFailure(extensibility.GetConfigMgr(this.project, VSConstants.VSITEMID_ROOT, out configurationManagerAsObject)); VsUtilities.CheckNotNull(configurationManagerAsObject); this.configurationManager = (ConfigurationManager)configurationManagerAsObject; } return this.configurationManager; } } /// /// Gets the Globals object containing add-in values that may be saved in the solution (.sln) file, the project file, or in the user's profile data. /// public virtual EnvDTE.Globals Globals { get { return null; } } /// /// Gets a ProjectItem object for the nested project in the host project. /// public virtual EnvDTE.ProjectItem ParentProjectItem { get { return null; } } /// /// Gets the CodeModel object for the project. /// public virtual EnvDTE.CodeModel CodeModel { get { return null; } } /// /// Saves the project. /// /// The file name with which to save the solution, project, or project item. If the file exists, it is overwritten /// Is thrown if the save operation failes. /// Is thrown if fileName is null. public virtual void SaveAs(string fileName) { UIThread.Instance.RunSync(() => { this.DoSave(true, fileName); }); } /// /// Saves the project /// /// The file name of the project /// Is thrown if the save operation failes. /// Is thrown if fileName is null. public virtual void Save(string fileName) { UIThread.Instance.RunSync(() => { this.DoSave(false, fileName); }); } /// /// Removes the project from the current solution. /// public virtual void Delete() { CheckProjectIsValid(); using (AutomationScope scope = new AutomationScope(this.project.Site)) { UIThread.Instance.RunSync(() => { this.project.Remove(false); }); } } #endregion #region ISupportVSProperties methods /// /// Microsoft public Use Only. /// public virtual void NotifyPropertiesDelete() { } #endregion #region private methods /// /// Saves or Save Asthe project. /// /// Flag determining which Save method called , the SaveAs or the Save. /// The name of the project file. private void DoSave(bool isCalledFromSaveAs, string fileName) { VsUtilities.ArgumentNotNull("fileName", fileName); CheckProjectIsValid(); using (AutomationScope scope = new AutomationScope(this.project.Site)) { // 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; VsUtilities.CheckNotNull(rdt); IVsHierarchy hier; uint itemid; IntPtr unkData; uint cookie; ErrorHandler.ThrowOnFailure(rdt.FindAndLockDocument((uint)_VSRDTFLAGS.RDT_NoLock, this.project.Url, out hier, out itemid, out unkData, out cookie)); 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.GetOuterInterface(); // Now get the soulution. IVsSolution solution = this.project.Site.GetService(typeof(SVsSolution)) as IVsSolution; // Verify that we have both solution and hierarchy. VsUtilities.CheckNotNull(prjHierarchy); VsUtilities.CheckNotNull(solution); 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 necessary 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 { fullPath = CommonUtils.GetAbsoluteFilePath(((ProjectNode)Project).ProjectFolder, fileName); } // We want to be consistent in the error message and exception we throw. fileName could be for example #¤&%"¤&"% and that would trigger an ArgumentException on Path.IsRooted. catch (ArgumentException ex) { throw new InvalidOperationException(String.Format(SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture), fileName), ex); } // 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. VsUtilities.ValidateFileName(this.project.Site, fullPath); if (!isCalledFromSaveAs) { // 2. The file name has to be the same if (!CommonUtils.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)); } } } } #endregion } /// /// Specifies an alternate name for a property which cannot be fully captured using /// .NET attribute names. /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] class PropertyNameAttribute : Attribute { public readonly string Name; public PropertyNameAttribute(string name) { Name = name; } } }