PageRenderTime 57ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Gemini/Modules/Shell/Services/LayoutItemStatePersister.cs

https://gitlab.com/Katze/gemini
C# | 214 lines | 161 code | 37 blank | 16 comment | 27 complexity | fef93d2cfcc0068c882809b218244e59 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.Composition;
  4. using System.IO;
  5. using System.Linq;
  6. using Caliburn.Micro;
  7. using Gemini.Framework;
  8. using Gemini.Framework.Services;
  9. using Gemini.Modules.Shell.Views;
  10. namespace Gemini.Modules.Shell.Services
  11. {
  12. [Export(typeof(ILayoutItemStatePersister))]
  13. public class LayoutItemStatePersister : ILayoutItemStatePersister
  14. {
  15. public void SaveState(IShell shell, IShellView shellView, string fileName)
  16. {
  17. FileStream stream = null;
  18. try
  19. {
  20. stream = new FileStream(fileName, FileMode.Create, FileAccess.Write);
  21. using (var writer = new BinaryWriter(stream))
  22. {
  23. stream = null;
  24. IEnumerable<ILayoutItem> itemStates = shell.Documents.Concat(shell.Tools.Cast<ILayoutItem>());
  25. int itemCount = 0;
  26. // reserve some space for items count, it'll be updated later
  27. writer.Write(itemCount);
  28. foreach (var item in itemStates)
  29. {
  30. if (!item.ShouldReopenOnStart)
  31. continue;
  32. var itemType = item.GetType();
  33. List<ExportAttribute> exportAttributes = itemType
  34. .GetCustomAttributes(typeof(ExportAttribute), false)
  35. .Cast<ExportAttribute>().ToList();
  36. var layoutType = typeof(ILayoutItem);
  37. // get exports with explicit types or names that inherit from ILayoutItem
  38. var exportTypes = (from att in exportAttributes
  39. // select the contract type if it is of type ILayoutitem. else null
  40. let typeFromContract = att.ContractType != null
  41. && layoutType.IsAssignableFrom(att.ContractType) ? att.ContractType : null
  42. // select the contract name if it is of type ILayoutItem. else null
  43. let typeFromQualifiedName = GetTypeFromContractNameAsILayoutItem(att)
  44. // select the viewmodel tpye if it is of type ILayoutItem. else null
  45. let typeFromViewModel = layoutType.IsAssignableFrom(itemType) ? itemType : null
  46. // att.ContractType overrides att.ContractName if both are set.
  47. // fall back to the ViewModel type of neither are defined.
  48. let type = typeFromContract ?? typeFromQualifiedName ?? typeFromViewModel
  49. where type != null
  50. select type).ToList();
  51. // throw exceptions here, instead of failing silently. These are design time errors.
  52. var firstExport = exportTypes.FirstOrDefault();
  53. if (firstExport == null)
  54. throw new InvalidOperationException(string.Format(
  55. "A ViewModel that participates in LayoutItem.ShouldReopenOnStart must be decorated with an ExportAttribute who's ContractType that inherits from ILayoutItem, infringing type is {0}.", itemType));
  56. if (exportTypes.Count > 1)
  57. throw new InvalidOperationException(string.Format(
  58. "A ViewModel that participates in LayoutItem.ShouldReopenOnStart can't be decorated with more than one ExportAttribute which inherits from ILayoutItem. infringing type is {0}.", itemType));
  59. var selectedTypeName = firstExport.AssemblyQualifiedName;
  60. if (string.IsNullOrEmpty(selectedTypeName))
  61. throw new InvalidOperationException(string.Format(
  62. "Could not retrieve the assembly qualified type name for {0}, most likely because the type is generic.", firstExport));
  63. // TODO: it is possible to save generic types. It requires that every generic parameter is saved, along with its position in the generic tree... A lot of work.
  64. writer.Write(selectedTypeName);
  65. writer.Write(item.ContentId);
  66. // Here's the tricky part. Because some items might fail to save their state, or they might be removed (a plug-in assembly deleted and etc.)
  67. // we need to save the item's state size to be able to skip the data during deserialization.
  68. // Save current stream position. We'll need it later.
  69. long stateSizePosition = writer.BaseStream.Position;
  70. // Reserve some space for item state size
  71. writer.Write(0L);
  72. long stateSize;
  73. try
  74. {
  75. long stateStartPosition = writer.BaseStream.Position;
  76. item.SaveState(writer);
  77. stateSize = writer.BaseStream.Position - stateStartPosition;
  78. }
  79. catch
  80. {
  81. stateSize = 0;
  82. }
  83. // Go back to the position before item's state and write the actual value.
  84. writer.BaseStream.Seek(stateSizePosition, SeekOrigin.Begin);
  85. writer.Write(stateSize);
  86. if (stateSize > 0)
  87. {
  88. // Got to the end of the stream
  89. writer.BaseStream.Seek(0, SeekOrigin.End);
  90. }
  91. itemCount++;
  92. }
  93. writer.BaseStream.Seek(0, SeekOrigin.Begin);
  94. writer.Write(itemCount);
  95. writer.BaseStream.Seek(0, SeekOrigin.End);
  96. shellView.SaveLayout(writer.BaseStream);
  97. }
  98. }
  99. catch
  100. {
  101. if (stream != null)
  102. {
  103. stream.Dispose();
  104. }
  105. }
  106. }
  107. Type GetTypeFromContractNameAsILayoutItem(ExportAttribute attribute)
  108. {
  109. if (attribute == null)
  110. return null;
  111. string typeName;
  112. if ((typeName = attribute.ContractName) == null)
  113. return null;
  114. var type = Type.GetType(typeName);
  115. if (type == null || !typeof(ILayoutItem).IsInstanceOfType(type))
  116. return null;
  117. return type;
  118. }
  119. public void LoadState(IShell shell, IShellView shellView, string fileName)
  120. {
  121. var layoutItems = new Dictionary<string, ILayoutItem>();
  122. if (!File.Exists(fileName))
  123. {
  124. return;
  125. }
  126. FileStream stream = null;
  127. try
  128. {
  129. stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
  130. using (var reader = new BinaryReader(stream))
  131. {
  132. stream = null;
  133. int count = reader.ReadInt32();
  134. for (int i = 0; i < count; i++)
  135. {
  136. string typeName = reader.ReadString();
  137. string contentId = reader.ReadString();
  138. long stateEndPosition = reader.ReadInt64();
  139. stateEndPosition += reader.BaseStream.Position;
  140. var contentType = Type.GetType(typeName);
  141. bool skipStateData = true;
  142. if (contentType != null)
  143. {
  144. var contentInstance = IoC.GetInstance(contentType, null) as ILayoutItem;
  145. if (contentInstance != null)
  146. {
  147. layoutItems.Add(contentId, contentInstance);
  148. try
  149. {
  150. contentInstance.LoadState(reader);
  151. skipStateData = false;
  152. }
  153. catch
  154. {
  155. skipStateData = true;
  156. }
  157. }
  158. }
  159. // Skip state data block if we couldn't read it.
  160. if (skipStateData)
  161. {
  162. reader.BaseStream.Seek(stateEndPosition, SeekOrigin.Begin);
  163. }
  164. }
  165. shellView.LoadLayout(reader.BaseStream, shell.ShowTool, shell.OpenDocument, layoutItems);
  166. }
  167. }
  168. catch
  169. {
  170. if (stream != null)
  171. {
  172. stream.Close();
  173. }
  174. }
  175. }
  176. }
  177. }