PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/BlogEngine/DotNetSlave.BusinessLogic/Web/Extensions/ExtensionManager.cs

#
C# | 534 lines | 266 code | 52 blank | 216 comment | 48 complexity | 23b6ce64e86335bbaa1d6877f8388075 MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, BSD-3-Clause
  1. namespace BlogEngine.Core.Web.Extensions
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Collections.Specialized;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Web;
  10. using System.Web.Hosting;
  11. using System.Xml.Serialization;
  12. using BlogEngine.Core.Web.Controls;
  13. /// <summary>
  14. /// Extension Manager - top level object in the hierarchy
  15. /// Holds collection of extensions and methods to manipulate
  16. /// extensions
  17. /// </summary>
  18. [XmlRoot]
  19. public class ExtensionManager
  20. {
  21. #region Constants and Fields
  22. /// <summary>
  23. /// The extensions.
  24. /// </summary>
  25. private static Dictionary<string, ManagedExtension> extensions = new Dictionary<string, ManagedExtension>();
  26. /// <summary>
  27. /// The new extensions.
  28. /// </summary>
  29. private static StringCollection newExtensions = new StringCollection();
  30. #endregion
  31. #region Properties
  32. /// <summary>
  33. /// Gets a collection of extensions
  34. /// </summary>
  35. [XmlElement]
  36. public static Dictionary<string, ManagedExtension> Extensions
  37. {
  38. get { return extensions; }
  39. }
  40. /// <summary>
  41. /// Gets or sets an exeption thrown when extension can not be serialized because of
  42. /// file access permission. Not serializable, used by UI to show error message.
  43. /// </summary>
  44. [XmlIgnore]
  45. public static Exception FileAccessException { get; set; }
  46. #endregion
  47. #region Public Methods
  48. /// <summary>
  49. /// Method to change extension status
  50. /// </summary>
  51. /// <param name="extension">
  52. /// Extensio Name
  53. /// </param>
  54. /// <param name="enabled">
  55. /// If true, enables extension
  56. /// </param>
  57. public static void ChangeStatus(string extension, bool enabled)
  58. {
  59. foreach (var x in extensions.Where(x => x.Key == extension))
  60. {
  61. x.Value.Enabled = enabled;
  62. var xs = new DataStore.ExtensionSettings(x.Key);
  63. xs.SaveSettings(x.Value);
  64. SaveToCache();
  65. var configPath = string.Format("{0}Web.Config", HostingEnvironment.ApplicationPhysicalPath);
  66. File.SetLastWriteTimeUtc(configPath, DateTime.UtcNow);
  67. break;
  68. }
  69. }
  70. /// <summary>
  71. /// Tell if manager already has this extension
  72. /// </summary>
  73. /// <param name="type">
  74. /// Extension Type
  75. /// </param>
  76. /// <returns>
  77. /// True if already has
  78. /// </returns>
  79. public static bool Contains(Type type)
  80. {
  81. return extensions.Any(extension => extension.Key == type.Name);
  82. }
  83. /// <summary>
  84. /// Enabled / Disabled
  85. /// </summary>
  86. /// <param name="extensionName">
  87. /// Name of the extension.
  88. /// </param>
  89. /// <returns>
  90. /// True if enabled
  91. /// </returns>
  92. public static bool ExtensionEnabled(string extensionName)
  93. {
  94. LoadExtensions();
  95. ManagedExtension extension = GetExtension(extensionName);
  96. if (Blog.CurrentInstance.IsPrimary)
  97. {
  98. return extension == null ? false : extension.Enabled;
  99. }
  100. else
  101. {
  102. if (extension != null)
  103. {
  104. // if the extension is disabled at the primary blog, then
  105. // it is automatically considered disabled for all child blogs.
  106. if (!extension.Enabled) { return false; }
  107. if (extension.Blogs != null && extension.Blogs.Contains(Blog.CurrentInstance.Id))
  108. {
  109. // when non-primary blog disables extension,
  110. // this blog added to Blogs list for this extension
  111. return false;
  112. }
  113. }
  114. return true;
  115. }
  116. }
  117. /// <summary>
  118. /// Returns extension to the client based on name
  119. /// </summary>
  120. /// <param name="extensionName">
  121. /// Extension name
  122. /// </param>
  123. /// <returns>
  124. /// Extension object or null
  125. /// </returns>
  126. public static ManagedExtension GetExtension(string extensionName)
  127. {
  128. ManagedExtension extension;
  129. extensions.TryGetValue(extensionName, out extension);
  130. return extension;
  131. }
  132. /// <summary>
  133. /// Method to get settings collection
  134. /// </summary>
  135. /// <param name="extensionName">
  136. /// Extension Name
  137. /// </param>
  138. /// <returns>
  139. /// Collection of settings
  140. /// </returns>
  141. public static ExtensionSettings GetSettings(string extensionName)
  142. {
  143. return extensions.SelectMany(
  144. x => x.Value.Settings.Where(setting => setting != null && setting.Name == extensionName)).FirstOrDefault();
  145. }
  146. /// <summary>
  147. /// Returns settings for specified extension
  148. /// </summary>
  149. /// <param name="extensionName">
  150. /// Extension Name
  151. /// </param>
  152. /// <param name="settingName">
  153. /// Settings Name
  154. /// </param>
  155. /// <returns>
  156. /// Settings object
  157. /// </returns>
  158. public static ExtensionSettings GetSettings(string extensionName, string settingName)
  159. {
  160. ManagedExtension extension = GetExtension(extensionName);
  161. if (extension == null)
  162. return null;
  163. return extension.Settings.Where(setting => setting != null && setting.Name == settingName).
  164. FirstOrDefault();
  165. }
  166. /// <summary>
  167. /// Do initial import here.
  168. /// If already imported, let extension manager take care of settings
  169. /// To reset, blogger has to delete all settings in the manager
  170. /// </summary>
  171. /// <param name="settings">
  172. /// The settings.
  173. /// </param>
  174. /// <returns>
  175. /// The import settings.
  176. /// </returns>
  177. public static bool ImportSettings(ExtensionSettings settings)
  178. {
  179. return ImportSettings(settings.Name, settings);
  180. }
  181. /// <summary>
  182. /// Imports the settings.
  183. /// </summary>
  184. /// <param name="extensionName">
  185. /// Name of the extension.
  186. /// </param>
  187. /// <param name="settings">
  188. /// The settings.
  189. /// </param>
  190. /// <returns>
  191. /// If import settings works.
  192. /// </returns>
  193. public static bool ImportSettings(string extensionName, ExtensionSettings settings)
  194. {
  195. var ext = extensions.FirstOrDefault(x => x.Key == extensionName && !x.Value.Initialized(settings));
  196. if (ext.Value != null)
  197. {
  198. ext.Value.InitializeSettings(settings);
  199. }
  200. SaveToCache();
  201. // return SaveToStorage();
  202. return true;
  203. }
  204. /// <summary>
  205. /// Initializes settings by importing default parameters
  206. /// </summary>
  207. /// <param name="extensionName">
  208. /// Extension Name
  209. /// </param>
  210. /// <param name="settings">
  211. /// Settings object
  212. /// </param>
  213. /// <returns>
  214. /// The Extension Settings
  215. /// </returns>
  216. public static ExtensionSettings InitSettings(string extensionName, ExtensionSettings settings)
  217. {
  218. ImportSettings(extensionName, settings);
  219. return GetSettings(extensionName, settings.Name);
  220. }
  221. /// <summary>
  222. /// Will serialize and cache ext. mgr. object
  223. /// </summary>
  224. public static void Save()
  225. {
  226. SaveToStorage();
  227. SaveToCache();
  228. }
  229. /// <summary>
  230. /// Will save settings (add to extension object, then
  231. /// cache and serialize all object hierarhy to XML)
  232. /// </summary>
  233. /// <param name="settings">
  234. /// Settings object
  235. /// </param>
  236. public static void SaveSettings(ExtensionSettings settings)
  237. {
  238. SaveSettings(settings.Name, settings);
  239. }
  240. /// <summary>
  241. /// The save settings.
  242. /// </summary>
  243. /// <param name="extensionName">
  244. /// The extension name.
  245. /// </param>
  246. /// <param name="settings">
  247. /// The settings.
  248. /// </param>
  249. public static void SaveSettings(string extensionName, ExtensionSettings settings)
  250. {
  251. var ext = extensions.FirstOrDefault(x => x.Key == extensionName);
  252. if (ext.Value != null)
  253. {
  254. ext.Value.SaveSettings(settings);
  255. }
  256. Save();
  257. }
  258. /// <summary>
  259. /// Saves ext. manager object to XML file
  260. /// or database table using provider model
  261. /// </summary>
  262. /// <returns>
  263. /// True if successful
  264. /// </returns>
  265. public static bool SaveToStorage()
  266. {
  267. foreach (var ext in extensions)
  268. {
  269. var xs = new DataStore.ExtensionSettings(ext.Key);
  270. xs.SaveSettings(ext.Value);
  271. }
  272. return true;
  273. }
  274. /// <summary>
  275. /// Save individual extension to storage
  276. /// </summary>
  277. /// <param name="ext">
  278. /// The Extension
  279. /// </param>
  280. /// <returns>
  281. /// True if saved
  282. /// </returns>
  283. public static bool SaveToStorage(ManagedExtension ext)
  284. {
  285. var xs = new DataStore.ExtensionSettings(ext.Name);
  286. xs.SaveSettings(ext);
  287. return true;
  288. }
  289. /// <summary>
  290. /// A way to let extension author to use custom
  291. /// admin page. Will show up as link on extensions page
  292. /// </summary>
  293. /// <param name="extension">
  294. /// Extension Name
  295. /// </param>
  296. /// <param name="url">
  297. /// Path to custom admin page
  298. /// </param>
  299. public static void SetAdminPage(string extension, string url)
  300. {
  301. var x = extensions.FirstOrDefault(ext => ext.Key == extension);
  302. if (x.Value == null)
  303. {
  304. return;
  305. }
  306. x.Value.AdminPage = url;
  307. SaveToStorage();
  308. SaveToCache();
  309. }
  310. /// <summary>
  311. /// Only change status on first load;
  312. /// This allows to enable/disable extension on
  313. /// initial load and then be able to override it with
  314. /// change status from admin interface
  315. /// </summary>
  316. /// <param name="extension">
  317. /// Extension Name
  318. /// </param>
  319. /// <param name="enabled">
  320. /// Enable/disable extension on initial load
  321. /// </param>
  322. public static void SetStatus(string extension, bool enabled)
  323. {
  324. if (IsNewExtension(extension))
  325. {
  326. ChangeStatus(extension, enabled);
  327. }
  328. }
  329. /// <summary>
  330. /// Show of hide settings in the admin/extensions list
  331. /// </summary>
  332. /// <param name="extensionName">
  333. /// Extension name
  334. /// </param>
  335. /// <param name="flag">
  336. /// True of false
  337. /// </param>
  338. public static void ShowSettings(string extensionName, bool flag)
  339. {
  340. var extension = extensions.FirstOrDefault(ext => ext.Key == extensionName);
  341. if (extension.Value == null)
  342. {
  343. return;
  344. }
  345. extension.Value.ShowSettings = flag;
  346. Save();
  347. }
  348. #endregion
  349. #region Methods
  350. /// <summary>
  351. /// Returns extension object
  352. /// </summary>
  353. /// <param name="name">
  354. /// Extension name
  355. /// </param>
  356. /// <returns>
  357. /// The Extension
  358. /// </returns>
  359. private static ManagedExtension DataStoreExtension(string name)
  360. {
  361. ManagedExtension ex = null;
  362. var xs = new DataStore.ExtensionSettings(name);
  363. var serializer = new XmlSerializer(typeof(ManagedExtension));
  364. var o = xs.GetSettings();
  365. if (o != null)
  366. {
  367. if (o.GetType().Name == "FileStream")
  368. {
  369. Stream stm = (FileStream)o;
  370. ex = (ManagedExtension)serializer.Deserialize(stm);
  371. stm.Close();
  372. }
  373. else
  374. {
  375. if (!string.IsNullOrEmpty((string)o))
  376. {
  377. using (var reader = new StringReader(o.ToString()))
  378. {
  379. ex = (ManagedExtension)serializer.Deserialize(reader);
  380. }
  381. }
  382. }
  383. }
  384. return ex;
  385. }
  386. /// <summary>
  387. /// Extension is "new" if it is loaded from assembly
  388. /// but not yet saved to the disk. This state is needed
  389. /// so that we can initialize extension and its settings
  390. /// on the first load and then override it from admin
  391. /// </summary>
  392. /// <param name="name">
  393. /// Extension name
  394. /// </param>
  395. /// <returns>
  396. /// True if new
  397. /// </returns>
  398. private static bool IsNewExtension(string name)
  399. {
  400. return newExtensions.Contains(name);
  401. }
  402. /// <summary>
  403. /// If extensions not in the cache will load
  404. /// from the XML file. If file not exists
  405. /// will load from assembly using reflection
  406. /// </summary>
  407. private static void LoadExtensions()
  408. {
  409. if (HttpRuntime.Cache["Extensions"] != null &&
  410. ((Dictionary<string, ManagedExtension>)HttpRuntime.Cache["Extensions"]).Count != 0)
  411. {
  412. return;
  413. }
  414. var codeAssemblies = Utils.CodeAssemblies();
  415. var meta = DataStoreExtension("MetaExtension");
  416. if (meta == null)
  417. {
  418. extensions.Add("MetaExtension", new ManagedExtension("MetaExtension", "1.0", "Meta extension", "BlogEngine.net"));
  419. }
  420. else
  421. {
  422. if (!extensions.ContainsKey("MetaExtension"))
  423. {
  424. extensions.Add("MetaExtension", meta);
  425. }
  426. }
  427. foreach (Assembly a in codeAssemblies)
  428. {
  429. var types = a.GetTypes();
  430. foreach (var type in types)
  431. {
  432. var attributes = type.GetCustomAttributes(typeof(ExtensionAttribute), false);
  433. foreach (var xa in attributes.Cast<ExtensionAttribute>())
  434. {
  435. // try to load from storage
  436. try
  437. {
  438. var x = DataStoreExtension(type.Name);
  439. // if nothing, crete new extension
  440. if (x == null)
  441. {
  442. x = new ManagedExtension(type.Name, xa.Version, xa.Description, xa.Author);
  443. newExtensions.Add(type.Name);
  444. SaveToStorage(x);
  445. }
  446. else
  447. {
  448. // update attributes from assembly
  449. x.Version = xa.Version;
  450. x.Description = xa.Description;
  451. x.Author = xa.Author;
  452. if (x.Priority == 0)
  453. {
  454. x.Priority = xa.Priority;
  455. }
  456. }
  457. if (!extensions.ContainsKey(x.Name))
  458. extensions.Add(x.Name, x);
  459. }
  460. catch (Exception e)
  461. {
  462. Utils.Log(string.Format("Can not load {0}: {1}", type.Name, e.Message));
  463. }
  464. }
  465. }
  466. }
  467. // SaveToStorage();
  468. SaveToCache();
  469. }
  470. /// <summary>
  471. /// Caches for performance. If manager cached
  472. /// and not updates done, chached copy always
  473. /// returned
  474. /// </summary>
  475. private static void SaveToCache()
  476. {
  477. HttpRuntime.Cache.Remove("Extensions");
  478. HttpRuntime.Cache["Extensions"] = extensions;
  479. }
  480. #endregion
  481. }
  482. }