PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/Studio/Nine.Studio/Project.cs

http://nine.codeplex.com
C# | 284 lines | 183 code | 43 blank | 58 comment | 26 complexity | 6b0abb7b605fc70804c476451a3b854e MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. namespace Nine.Studio
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Collections.ObjectModel;
  6. using System.ComponentModel;
  7. using System.Diagnostics;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Xml.Linq;
  11. using System.Xml.Serialization;
  12. using Nine.Studio.Extensibility;
  13. /// <summary>
  14. /// Represents project that manages multiple documents.
  15. /// </summary>
  16. public class Project : ObservableObject, IDisposable
  17. {
  18. #region Properties
  19. public static readonly string SourceFileExtension = ".nine";
  20. public static readonly string OutputFileExtension = ".n";
  21. /// <summary>
  22. /// Gets the fileName of this project.
  23. /// </summary>
  24. public string Name { get; private set; }
  25. /// <summary>
  26. /// Gets the absolute fileName of this project with full path.
  27. /// </summary>
  28. public string FileName { get; private set; }
  29. /// <summary>
  30. /// Gets the absolute directory of this project.
  31. /// </summary>
  32. public string Directory { get; private set; }
  33. /// <summary>
  34. /// Gets whether this project is ready only.
  35. /// </summary>
  36. public bool IsReadOnly { get; private set; }
  37. /// <summary>
  38. /// Gets whether this project is modified since last save.
  39. /// </summary>
  40. public bool IsModified
  41. {
  42. get { return isModified; }
  43. set
  44. {
  45. if (value != isModified)
  46. {
  47. isModified = value;
  48. NotifyPropertyChanged();
  49. }
  50. }
  51. }
  52. private bool isModified = true;
  53. /// <summary>
  54. /// Gets the containing editor instance.
  55. /// </summary>
  56. public Editor Editor { get; private set; }
  57. /// <summary>
  58. /// Gets or sets the active project item.
  59. /// </summary>
  60. public ProjectItem ActiveProjectItem
  61. {
  62. get { return activeProjectItem; }
  63. set
  64. {
  65. if (activeProjectItem != value)
  66. {
  67. if (value != null && (value.Project != this || !ProjectItems.Contains(value)))
  68. throw new InvalidOperationException();
  69. activeProjectItem = value;
  70. NotifyPropertyChanged();
  71. }
  72. }
  73. }
  74. private ProjectItem activeProjectItem;
  75. /// <summary>
  76. /// Gets a list of documents owned by this project.
  77. /// </summary>
  78. public ObservableCollection<ProjectItem> ProjectItems { get; private set; }
  79. /// <summary>
  80. /// Gets a collection of projects that is referenced by this project.
  81. /// </summary>
  82. public ObservableCollection<Project> References { get; private set; }
  83. #endregion
  84. #region Methods
  85. private Project(Editor editor)
  86. {
  87. Verify.IsNotNull(editor, "editor");
  88. this.Editor = editor;
  89. this.ProjectItems = new ObservableCollection<ProjectItem>();
  90. this.References = new ObservableCollection<Project>();
  91. this.ProjectItems.CollectionChanged += (sender, e) => { isModified = true; };
  92. this.References.CollectionChanged += (sender, e) => { isModified = true; };
  93. }
  94. internal Project(Editor editor, string name, string directory) : this(editor)
  95. {
  96. Verify.IsValidPath(directory, "directory");
  97. Verify.IsValidFileName(name, "name");
  98. Name = name;
  99. Directory = Path.GetFullPath(Path.Combine(directory, name));
  100. FileName = Path.Combine(Directory, name + SourceFileExtension);
  101. if (System.IO.Directory.Exists(Directory) &&
  102. (System.IO.Directory.GetFiles(Directory).Length > 0 ||
  103. System.IO.Directory.GetDirectories(Directory).Length > 0))
  104. {
  105. throw new ArgumentException(string.Format(Strings.DirectoryNotEmpty, name));
  106. }
  107. }
  108. internal Project(Editor editor, string fileName) : this(editor)
  109. {
  110. Verify.FileExists(fileName, "fileName");
  111. FileName = Path.GetFullPath(fileName);
  112. Name = Path.GetFileNameWithoutExtension(FileName);
  113. Directory = Path.GetDirectoryName(FileName);
  114. Load();
  115. FileInfo info = new FileInfo(FileName);
  116. IsReadOnly = info.Exists && info.IsReadOnly;
  117. }
  118. /// <summary>
  119. /// Creates a new project item from a factory.
  120. /// </summary>
  121. public ProjectItem Create(IFactory factory)
  122. {
  123. Verify.IsNotNull(factory, "factory");
  124. var result = new ProjectItem(this, factory.Create(this, null));
  125. ProjectItems.Add(result);
  126. return result;
  127. }
  128. /// <summary>
  129. /// Creates a new project item with the specified object model inside the target project.
  130. /// </summary>
  131. public ProjectItem Create(object objectModel)
  132. {
  133. var result = new ProjectItem(this, objectModel);
  134. ProjectItems.Add(result);
  135. return result;
  136. }
  137. /// <summary>
  138. /// Imports a project item from a source file to this project.
  139. /// </summary>
  140. /// <param name="fileName">
  141. /// The full path to the file to be imported, or a relative path relative to the project directory.
  142. /// </param>
  143. public ProjectItem Import(string fileName)
  144. {
  145. var fileExtension = FileHelper.NormalizeExtension(Path.GetExtension(fileName));
  146. var importer = Editor.Extensions.Importers.FirstOrDefault(
  147. i => i.Value.GetSupportedFileExtensions().Any(
  148. ext => FileHelper.NormalizeExtension(ext) == fileExtension));
  149. return importer != null ? Import(fileName, importer.Value) : null;
  150. }
  151. /// <summary>
  152. /// Imports a project item from a source file to this project.
  153. /// </summary>
  154. public ProjectItem Import(string fileName, IImporter importer)
  155. {
  156. var result = new ProjectItem(this, fileName, importer);
  157. ProjectItems.Add(result);
  158. return result;
  159. }
  160. /// <summary>
  161. /// Adds a new reference by this project.
  162. /// </summary>
  163. public void AddReference(Project project)
  164. {
  165. Verify.IsNotNull(project, "project");
  166. if (HasCircularDependency(project, this))
  167. throw new InvalidOperationException("Target object has a circular dependency");
  168. if (!References.Contains(project))
  169. References.Add(project);
  170. }
  171. /// <summary>
  172. /// Removes an existing reference by this project
  173. /// </summary>
  174. public void RemoveReference(Project project)
  175. {
  176. Verify.IsNotNull(project, "project");
  177. References.Remove(project);
  178. }
  179. private static bool HasCircularDependency(Project subtree, Project root)
  180. {
  181. return !subtree.References.All(node => node != root && !HasCircularDependency(node, root));
  182. }
  183. /// <summary>
  184. /// Closes the project.
  185. /// </summary>
  186. public void Close()
  187. {
  188. // Can't close this when any project references this one
  189. if (Editor.Projects.Any(project => project.References.Contains(this)))
  190. throw new InvalidOperationException("Cannot close project because it is referenced by other projects");
  191. ProjectItems.ForEach(doc => doc.Close());
  192. Editor.projects.Remove(this);
  193. References.ForEach(project => project.Close());
  194. if (Editor.ActiveProject == this)
  195. Editor.ActiveProject = null;
  196. Trace.TraceInformation("Project {0} closed.", Name);
  197. }
  198. private void Load()
  199. {
  200. var project = (Nine.Studio.Xaml.Project)System.Xaml.XamlServices.Load(FileName);
  201. if (Version.Parse(project.Version) > Version.Parse(Editor.VersionString))
  202. throw new InvalidOperationException(Strings.VersionNotSupported);
  203. project.References.ForEach(x => Editor.OpenProject(x.Source));
  204. project.ProjectItems.ForEach(x => Import(x.Source, x.Importer));
  205. }
  206. /// <summary>
  207. /// Saves the project.
  208. /// </summary>
  209. public void Save()
  210. {
  211. if (IsModified)
  212. FileHelper.BackupAndSave(FileName, SaveProject);
  213. foreach (ProjectItem projectItem in ProjectItems)
  214. {
  215. if (projectItem.IsModified && projectItem.CanSave)
  216. projectItem.Save();
  217. }
  218. IsModified = false;
  219. Editor.AddRecentProject(FileName);
  220. GC.Collect();
  221. GC.WaitForPendingFinalizers();
  222. }
  223. private void SaveProject(Stream stream)
  224. {
  225. var project = new Nine.Studio.Xaml.Project() { Version = Editor.VersionString };
  226. project.References.AddRange(References.Select(x => new Nine.Studio.Xaml.ProjectReference() { Source = x.FileName }));
  227. project.ProjectItems.AddRange(ProjectItems.Select(x => new Nine.Studio.Xaml.ProjectItem() { Source = x.RelativeFilename, Importer = x.Importer != null ? x.Importer.Value : null }));
  228. System.Xaml.XamlServices.Save(stream, project);
  229. }
  230. protected override void NotifyPropertyChanged(string propertyName = null)
  231. {
  232. isModified = true;
  233. base.NotifyPropertyChanged(propertyName);
  234. }
  235. void IDisposable.Dispose()
  236. {
  237. Close();
  238. }
  239. #endregion
  240. }
  241. }