PageRenderTime 96ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/BlogEngine/DotNetSlave.BusinessLogic/Blog.cs

#
C# | 994 lines | 625 code | 143 blank | 226 comment | 109 complexity | b95d234d9def0d062b34b342a87e247f MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, BSD-3-Clause
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web.Caching;
  5. using BlogEngine.Core.Providers;
  6. using System.Web;
  7. using System.Web.Hosting;
  8. using System.IO;
  9. using System.Text.RegularExpressions;
  10. using System.Net;
  11. using BlogEngine.Core.Providers.CacheProvider;
  12. namespace BlogEngine.Core
  13. {
  14. /// <summary>
  15. /// Represents a blog instance.
  16. /// </summary>
  17. public class Blog : BusinessBase<Blog, Guid>, IComparable<Blog>
  18. {
  19. /// <summary>
  20. /// Whether the blog is deleted.
  21. /// </summary>
  22. private bool isDeleted;
  23. /// <summary>
  24. /// Blog name
  25. /// </summary>
  26. private string blogName;
  27. /// <summary>
  28. /// Whether the blog is the primary blog instance
  29. /// </summary>
  30. private bool isPrimary;
  31. /// <summary>
  32. /// Whether the blog is active
  33. /// </summary>
  34. private bool isActive;
  35. /// <summary>
  36. /// The hostname of the blog instance.
  37. /// </summary>
  38. private string hostname;
  39. /// <summary>
  40. /// Whether any text before the hostname is accepted.
  41. /// </summary>
  42. private bool isAnyTextBeforeHostnameAccepted;
  43. /// <summary>
  44. /// The storage container name of the blog's data
  45. /// </summary>
  46. private string storageContainerName;
  47. /// <summary>
  48. /// The virtual path to the blog instance
  49. /// </summary>
  50. private string virtualPath;
  51. /// <summary>
  52. /// The relative web root.
  53. /// </summary>
  54. private string relativeWebRoot;
  55. /// <summary>
  56. /// Flag used when blog is deleted on whether the storage container will be deleted too.
  57. /// </summary>
  58. private bool deleteStorageContainer;
  59. /// <summary>
  60. /// The sync root.
  61. /// </summary>
  62. private static readonly object SyncRoot = new object();
  63. /// <summary>
  64. /// The blogs.
  65. /// </summary>
  66. private static List<Blog> blogs;
  67. /// <summary>
  68. /// Gets or sets a value indicating whether or not the blog is deleted.
  69. /// </summary>
  70. public bool IsDeleted
  71. {
  72. get
  73. {
  74. return this.isDeleted;
  75. }
  76. set
  77. {
  78. base.SetValue("IsDeleted", value, ref this.isDeleted);
  79. }
  80. }
  81. /// <summary>
  82. /// Gets whether the blog is the primary blog instance.
  83. /// </summary>
  84. public bool IsPrimary
  85. {
  86. get
  87. {
  88. return this.isPrimary;
  89. }
  90. internal set
  91. {
  92. // SetAsPrimaryInstance() exists as a public method to make
  93. // a blog instance be the primary one -- which makes sure other
  94. // instances are no longer primary.
  95. base.SetValue("IsPrimary", value, ref this.isPrimary);
  96. }
  97. }
  98. /// <summary>
  99. /// Gets whether the blog instance is active.
  100. /// </summary>
  101. public bool IsActive
  102. {
  103. get
  104. {
  105. return this.isActive;
  106. }
  107. set
  108. {
  109. base.SetValue("IsActive", value, ref this.isActive);
  110. }
  111. }
  112. /// <summary>
  113. /// Gets the optional hostname of the blog instance.
  114. /// </summary>
  115. public string Hostname
  116. {
  117. get
  118. {
  119. return this.hostname;
  120. }
  121. set
  122. {
  123. base.SetValue("Hostname", value, ref this.hostname);
  124. }
  125. }
  126. /// <summary>
  127. /// Gets whether any text before the hostname is accepted.
  128. /// </summary>
  129. public bool IsAnyTextBeforeHostnameAccepted
  130. {
  131. get
  132. {
  133. return this.isAnyTextBeforeHostnameAccepted;
  134. }
  135. set
  136. {
  137. base.SetValue("IsAnyTextBeforeHostnameAccepted", value, ref this.isAnyTextBeforeHostnameAccepted);
  138. }
  139. }
  140. /// <summary>
  141. /// Gets or sets the blog name.
  142. /// </summary>
  143. public string Name
  144. {
  145. get
  146. {
  147. return this.blogName;
  148. }
  149. set
  150. {
  151. base.SetValue("Name", value, ref this.blogName);
  152. }
  153. }
  154. /// <summary>
  155. /// Gets or sets the storage container name.
  156. /// </summary>
  157. public string StorageContainerName
  158. {
  159. get
  160. {
  161. return this.storageContainerName;
  162. }
  163. set
  164. {
  165. base.SetValue("StorageContainerName", value, ref this.storageContainerName);
  166. }
  167. }
  168. /// <summary>
  169. /// Gets or sets the virtual path to the blog instance.
  170. /// </summary>
  171. public string VirtualPath
  172. {
  173. get
  174. {
  175. return this.virtualPath;
  176. }
  177. set
  178. {
  179. // RelativeWebRoot is based on VirtualPath. Clear relativeWebRoot
  180. // so RelativeWebRoot is re-generated.
  181. this.relativeWebRoot = null;
  182. base.SetValue("VirtualPath", value, ref this.virtualPath);
  183. }
  184. }
  185. /// <summary>
  186. /// Flag used when blog is deleted on whether the storage container will be deleted too.
  187. /// This property is not peristed.
  188. /// </summary>
  189. public bool DeleteStorageContainer
  190. {
  191. get
  192. {
  193. return this.deleteStorageContainer;
  194. }
  195. set
  196. {
  197. base.SetValue("DeleteStorageContainer", value, ref this.deleteStorageContainer);
  198. }
  199. }
  200. public bool IsSubfolderOfApplicationWebRoot
  201. {
  202. get
  203. {
  204. return this.RelativeWebRoot.Length > Utils.ApplicationRelativeWebRoot.Length;
  205. }
  206. }
  207. public override void Delete()
  208. {
  209. if (this.IsPrimary)
  210. {
  211. throw new Exception("The primary blog cannot be deleted.");
  212. }
  213. base.Delete();
  214. }
  215. /// <summary>
  216. /// Deletes the Blog from the current BlogProvider.
  217. /// </summary>
  218. protected override void DataDelete()
  219. {
  220. OnSaving(this, SaveAction.Delete);
  221. if (this.DeleteStorageContainer)
  222. {
  223. BlogService.DeleteBlogStorageContainer(this);
  224. }
  225. if (this.Deleted)
  226. {
  227. BlogService.DeleteBlog(this);
  228. }
  229. Blogs.Remove(this);
  230. SortBlogs();
  231. OnSaved(this, SaveAction.Delete);
  232. this.Dispose();
  233. }
  234. /// <summary>
  235. /// Inserts a new blog to the current BlogProvider.
  236. /// </summary>
  237. protected override void DataInsert()
  238. {
  239. OnSaving(this, SaveAction.Insert);
  240. if (this.New)
  241. {
  242. BlogService.InsertBlog(this);
  243. }
  244. Blogs.Add(this);
  245. SortBlogs();
  246. OnSaved(this, SaveAction.Insert);
  247. }
  248. /// <summary>
  249. /// Updates the object in its data store.
  250. /// </summary>
  251. protected override void DataUpdate()
  252. {
  253. OnSaving(this, SaveAction.Update);
  254. if (this.IsChanged)
  255. {
  256. BlogService.UpdateBlog(this);
  257. SortBlogs();
  258. }
  259. OnSaved(this, SaveAction.Update);
  260. }
  261. /// <summary>
  262. /// Retrieves the object from the data store and populates it.
  263. /// </summary>
  264. /// <param name="id">
  265. /// The unique identifier of the object.
  266. /// </param>
  267. /// <returns>
  268. /// The object that was selected from the data store.
  269. /// </returns>
  270. protected override Blog DataSelect(Guid id)
  271. {
  272. return BlogService.SelectBlog(id);
  273. }
  274. /// <summary>
  275. /// Reinforces the business rules by adding additional rules to the
  276. /// broken rules collection.
  277. /// </summary>
  278. protected override void ValidationRules()
  279. {
  280. this.AddRule("Name", "Name must be set", string.IsNullOrEmpty(this.Name));
  281. }
  282. /// <summary>
  283. /// Gets whether the current user can delete this object.
  284. /// </summary>
  285. public override bool CanUserDelete
  286. {
  287. get
  288. {
  289. return Security.IsAdministrator && !this.IsPrimary;
  290. }
  291. }
  292. public void SetAsPrimaryInstance()
  293. {
  294. for (int i = 0; i < Blogs.Count; i++)
  295. {
  296. // Ensure other blogs are not marked as primary.
  297. if (Blogs[i].Id != this.Id && Blogs[i].IsPrimary)
  298. {
  299. Blogs[i].IsPrimary = false;
  300. Blogs[i].Save();
  301. }
  302. else if (Blogs[i].Id == this.Id)
  303. {
  304. Blogs[i].IsPrimary = true;
  305. Blogs[i].Save();
  306. }
  307. }
  308. }
  309. /// <summary>
  310. /// Initializes a new instance of the <see cref = "Blog" /> class.
  311. /// The default contstructor assign default values.
  312. /// </summary>
  313. public Blog()
  314. {
  315. this.Id = Guid.NewGuid();
  316. this.DateCreated = DateTime.Now;
  317. this.DateModified = DateTime.Now;
  318. }
  319. /// <summary>
  320. /// Gets all blogs.
  321. /// </summary>
  322. public static List<Blog> Blogs
  323. {
  324. get
  325. {
  326. if (blogs == null)
  327. {
  328. lock (SyncRoot)
  329. {
  330. if (blogs == null)
  331. {
  332. blogs = BlogService.FillBlogs().ToList();
  333. if (blogs.Count == 0)
  334. {
  335. // create the primary instance
  336. Blog blog = new Blog();
  337. blog.Name = "Primary";
  338. blog.hostname = string.Empty;
  339. blog.VirtualPath = BlogConfig.VirtualPath;
  340. blog.StorageContainerName = string.Empty;
  341. blog.IsPrimary = true;
  342. blog.Save();
  343. }
  344. SortBlogs();
  345. }
  346. }
  347. }
  348. return blogs;
  349. }
  350. }
  351. private static void SortBlogs()
  352. {
  353. Blogs.Sort();
  354. }
  355. /// <summary>
  356. /// Marked as ThreadStatic so each thread has its own value.
  357. /// Need to be careful with this since when using ThreadPool.QueueUserWorkItem,
  358. /// after a thread is used, it is returned to the thread pool and
  359. /// any ThreadStatic values (such as this field) are not cleared, they will persist.
  360. ///
  361. /// This value is reset in WwwSubdomainModule.BeginRequest.
  362. ///
  363. /// </summary>
  364. [ThreadStatic]
  365. private static Guid _InstanceIdOverride;
  366. /// <summary>
  367. /// This is a thread-specific Blog Instance ID to override.
  368. /// If the current blog instance needs to be overridden,
  369. /// this property can be used. A typical use for this is when
  370. /// using BG/async threads where the current blog instance
  371. /// cannot be determined since HttpContext will be null.
  372. /// </summary>
  373. public static Guid InstanceIdOverride
  374. {
  375. get { return _InstanceIdOverride; }
  376. set { _InstanceIdOverride = value; }
  377. }
  378. /// <summary>
  379. /// The current blog instance.
  380. /// </summary>
  381. public static Blog CurrentInstance
  382. {
  383. get
  384. {
  385. if (_InstanceIdOverride != Guid.Empty)
  386. {
  387. Blog overrideBlog = Blogs.FirstOrDefault(b => b.Id == _InstanceIdOverride);
  388. if (overrideBlog != null)
  389. {
  390. return overrideBlog;
  391. }
  392. }
  393. const string CONTEXT_ITEM_KEY = "current-blog-instance";
  394. HttpContext context = HttpContext.Current;
  395. Blog blog = context.Items[CONTEXT_ITEM_KEY] as Blog;
  396. if (blog != null) { return blog; }
  397. List<Blog> blogs = Blogs;
  398. if (blogs.Count == 0) { return null; }
  399. if (blogs.Count == 1)
  400. {
  401. blog = blogs[0];
  402. }
  403. else
  404. {
  405. // Determine which blog.
  406. //
  407. // Web service and Page method calls to the server need to be made to the
  408. // root level, and cannot be virtual URLs that get rewritten to the correct
  409. // physical location. When attempting to rewrite these URLs, the web
  410. // service/page method will throw a "405 Method Not Allowed" error.
  411. // For us to determine which blog these AJAX calls are on behalf of,
  412. // a request header on the AJAX calls will be appended to tell us which
  413. // blog instance they are for.
  414. //
  415. // The built-in ASP.NET Callback system works correctly even when
  416. // the URL is rewritten. For these, CurrentInstance will be determined
  417. // and stored in HttpContext.Items before the rewrite is done -- so even
  418. // after the rewrite, CurrentInstance will reference the correct blog
  419. // instance.
  420. //
  421. string blogIdHeader = context.Request.Headers["x-blog-instance"];
  422. if (!string.IsNullOrWhiteSpace(blogIdHeader) && blogIdHeader.Length == 36)
  423. {
  424. blog = GetBlog(new Guid(blogIdHeader));
  425. if (blog != null && !blog.IsActive)
  426. blog = null;
  427. }
  428. if (blog == null)
  429. {
  430. // Note, this.Blogs is sorted via SortBlogs() so the blogs with longer
  431. // RelativeWebRoots come first. This is important when matching so the
  432. // more specific matches are done first.
  433. // for the purposes here, adding a trailing slash to RawUrl, even if it's not
  434. // a correct URL. if a blog has a relative root of /blog1, RelativeWebRoot
  435. // will be /blog1/ (containing the trailing slash). for equal comparisons,
  436. // make sure rawUrl also has a trailing slash.
  437. string rawUrl = VirtualPathUtility.AppendTrailingSlash(context.Request.RawUrl);
  438. string hostname = context.Request.Url.Host;
  439. for (int i = 0; i < blogs.Count; i++)
  440. {
  441. Blog checkBlog = blogs[i];
  442. if (checkBlog.isActive)
  443. {
  444. // first check the hostname, if a hostname is specified
  445. if (!string.IsNullOrWhiteSpace(checkBlog.hostname))
  446. {
  447. bool isMatch = false;
  448. if (checkBlog.IsAnyTextBeforeHostnameAccepted)
  449. isMatch = hostname.EndsWith(checkBlog.hostname, StringComparison.OrdinalIgnoreCase);
  450. else
  451. isMatch = hostname.Equals(checkBlog.hostname, StringComparison.OrdinalIgnoreCase);
  452. // if isMatch, we still need to check the conditions below, to allow
  453. // multiple path variations for a particular hostname.
  454. if (!isMatch)
  455. {
  456. continue;
  457. }
  458. }
  459. // second check the path.
  460. if (rawUrl.StartsWith(checkBlog.RelativeWebRoot, StringComparison.OrdinalIgnoreCase))
  461. {
  462. blog = checkBlog;
  463. break;
  464. }
  465. }
  466. }
  467. // if all blogs are inactive, or there are no matches for some reason,
  468. // select the primary blog.
  469. if (blog == null)
  470. {
  471. blog = blogs.FirstOrDefault(b => b.IsPrimary);
  472. }
  473. }
  474. }
  475. context.Items[CONTEXT_ITEM_KEY] = blog;
  476. return blog;
  477. }
  478. }
  479. /// <summary>
  480. /// Returns a blog based on the specified id.
  481. /// </summary>
  482. /// <param name="id">
  483. /// The blog id.
  484. /// </param>
  485. /// <returns>
  486. /// The selected blog.
  487. /// </returns>
  488. public static Blog GetBlog(Guid id)
  489. {
  490. return Blogs.Find(b => b.Id == id);
  491. }
  492. /// <summary>
  493. /// Gets a mappable virtual path to the blog instance's storage folder.
  494. /// </summary>
  495. public string StorageLocation
  496. {
  497. get
  498. {
  499. // only the Primary blog instance should have an empty StorageContainerName
  500. if (string.IsNullOrWhiteSpace(this.StorageContainerName))
  501. {
  502. return BlogConfig.StorageLocation;
  503. }
  504. return string.Format("{0}{1}/{2}/", BlogConfig.StorageLocation, BlogConfig.BlogInstancesFolderName, this.StorageContainerName);
  505. }
  506. }
  507. /// <summary>
  508. /// the root file storage directory for the blog. All File system management should start from the Root file store
  509. /// </summary>
  510. public FileSystem.Directory RootFileStore
  511. {
  512. get
  513. {
  514. return BlogService.GetDirectory(string.Concat(this.StorageLocation, "files"));
  515. }
  516. }
  517. /// <summary>
  518. /// Gets the relative root of the blog instance.
  519. /// </summary>
  520. /// <value>A string that ends with a '/'.</value>
  521. public string RelativeWebRoot
  522. {
  523. get
  524. {
  525. return relativeWebRoot ??
  526. (relativeWebRoot =
  527. VirtualPathUtility.ToAbsolute(VirtualPathUtility.AppendTrailingSlash(this.VirtualPath ?? BlogConfig.VirtualPath)));
  528. }
  529. }
  530. /// <summary>
  531. /// Gets the absolute root of the blog instance.
  532. /// </summary>
  533. public Uri AbsoluteWebRoot
  534. {
  535. get
  536. {
  537. string contextItemKey = string.Format("{0}-absolutewebroot", this.Id);
  538. var context = HttpContext.Current;
  539. if (context == null)
  540. {
  541. throw new WebException("The current HttpContext is null");
  542. }
  543. Uri absoluteWebRoot = context.Items[contextItemKey] as Uri;
  544. if (absoluteWebRoot != null) { return absoluteWebRoot; }
  545. UriBuilder uri = new UriBuilder();
  546. if (!string.IsNullOrWhiteSpace(this.Hostname))
  547. uri.Host = this.Hostname;
  548. else
  549. {
  550. uri.Host = context.Request.Url.Host;
  551. if (!context.Request.Url.IsDefaultPort)
  552. {
  553. uri.Port = context.Request.Url.Port;
  554. }
  555. }
  556. string vPath = this.VirtualPath ?? string.Empty;
  557. if (vPath.StartsWith("~/")) { vPath = vPath.Substring(2); }
  558. uri.Path = string.Format("{0}{1}", Utils.ApplicationRelativeWebRoot, vPath);
  559. if (!uri.Path.EndsWith("/")) { uri.Path += "/"; }
  560. absoluteWebRoot = uri.Uri;
  561. context.Items[contextItemKey] = absoluteWebRoot;
  562. return absoluteWebRoot;
  563. }
  564. }
  565. /// <summary>
  566. /// Creates a new blog.
  567. /// </summary>
  568. public static Blog CreateNewBlog(
  569. string copyFromExistingBlogId,
  570. string blogName,
  571. string hostname,
  572. bool isAnyTextBeforeHostnameAccepted,
  573. string storageContainerName,
  574. string virtualPath,
  575. bool isActive,
  576. out string message)
  577. {
  578. message = null;
  579. if (!ValidateProperties(true, null, blogName, hostname, isAnyTextBeforeHostnameAccepted, storageContainerName, virtualPath, out message))
  580. {
  581. if (string.IsNullOrWhiteSpace(message))
  582. {
  583. message = "Validation for new blog failed.";
  584. }
  585. return null;
  586. }
  587. if (string.IsNullOrWhiteSpace(copyFromExistingBlogId) || copyFromExistingBlogId.Length != 36)
  588. {
  589. message = "An existing blog instance ID must be specified to create the new blog from.";
  590. return null;
  591. }
  592. Blog existingBlog = Blog.GetBlog(new Guid(copyFromExistingBlogId));
  593. if (existingBlog == null)
  594. {
  595. message = "The existing blog instance to create the new blog from could not be found.";
  596. return null;
  597. }
  598. Blog newBlog = new Blog()
  599. {
  600. Name = blogName,
  601. StorageContainerName = storageContainerName,
  602. Hostname = hostname,
  603. IsAnyTextBeforeHostnameAccepted = isAnyTextBeforeHostnameAccepted,
  604. VirtualPath = virtualPath,
  605. IsActive = isActive
  606. };
  607. bool setupResult = false;
  608. try
  609. {
  610. setupResult = newBlog.SetupFromExistingBlog(existingBlog);
  611. }
  612. catch (Exception ex)
  613. {
  614. Utils.Log("Blog.CreateNewBlog", ex);
  615. message = "Failed to create new blog. Error: " + ex.Message;
  616. return null;
  617. }
  618. if (!setupResult)
  619. {
  620. message = "Failed during process of setting up the blog from the existing blog instance.";
  621. return null;
  622. }
  623. // save the blog for the first time.
  624. newBlog.Save();
  625. return newBlog;
  626. }
  627. public static bool ValidateProperties(
  628. bool isNew,
  629. Blog updateBlog,
  630. string blogName,
  631. string hostname,
  632. bool isAnyTextBeforeHostnameAccepted,
  633. string storageContainerName,
  634. string virtualPath,
  635. out string message)
  636. {
  637. message = null;
  638. if (string.IsNullOrWhiteSpace(blogName))
  639. {
  640. message = "Blog Name is Required.";
  641. return false;
  642. }
  643. if (!string.IsNullOrWhiteSpace(hostname))
  644. {
  645. if (!Utils.IsHostnameValid(hostname) &&
  646. !Utils.IsIpV4AddressValid(hostname) &&
  647. !Utils.IsIpV6AddressValid(hostname))
  648. {
  649. message = "Invalid Hostname. Hostname must be an IP address or domain name.";
  650. return false;
  651. }
  652. }
  653. Regex validChars = new Regex("^[a-z0-9-_]+$", RegexOptions.IgnoreCase);
  654. // if primary is being edited, allow an empty storage container name (bypass check).
  655. if (updateBlog == null || !updateBlog.IsPrimary)
  656. {
  657. if (string.IsNullOrWhiteSpace(storageContainerName))
  658. {
  659. message = "Storage Container Name is Required.";
  660. return false;
  661. }
  662. }
  663. if (!string.IsNullOrWhiteSpace(storageContainerName) && !validChars.IsMatch(storageContainerName))
  664. {
  665. message = "Storage Container Name contains invalid characters.";
  666. return false;
  667. }
  668. if (string.IsNullOrWhiteSpace(virtualPath))
  669. {
  670. message = "Virtual Path is Required.";
  671. return false;
  672. }
  673. else
  674. {
  675. if (!virtualPath.StartsWith("~/"))
  676. {
  677. message = "Virtual Path must begin with ~/";
  678. return false;
  679. }
  680. // note: a virtual path of ~/ without anything after it is allowed. this would
  681. // typically be for the primary blog, but can also be for blogs that are using
  682. // subdomains, where each instance might be ~/
  683. string vPath = virtualPath.Substring(2);
  684. if (vPath.Length > 0)
  685. {
  686. if (!validChars.IsMatch(vPath))
  687. {
  688. message = "The Virtual Path contains invalid characters after the ~/";
  689. return false;
  690. }
  691. }
  692. }
  693. if (Blog.Blogs.FirstOrDefault(b => (updateBlog == null || updateBlog.Id != b.Id) && (b.VirtualPath ?? string.Empty).Equals((virtualPath ?? string.Empty), StringComparison.OrdinalIgnoreCase) && (b.Hostname ?? string.Empty).Equals(hostname ?? string.Empty, StringComparison.OrdinalIgnoreCase)) != null)
  694. {
  695. message = "Another blog has the same combination of Hostname and Virtual Path.";
  696. return false;
  697. }
  698. return true;
  699. }
  700. /// <summary>
  701. /// Sets up the blog instance using the files and settings from an existing blog instance.
  702. /// </summary>
  703. /// <param name="existing">The existing blog instance to use files and settings from.</param>
  704. /// <returns></returns>
  705. public bool SetupFromExistingBlog(Blog existing)
  706. {
  707. if (existing == null)
  708. throw new ArgumentException("existing");
  709. if (string.IsNullOrWhiteSpace(this.StorageContainerName))
  710. throw new ArgumentException("this.StorageContainerName");
  711. // allow the blog provider to setup the necessary blog files, etc.
  712. bool providerResult = BlogService.SetupBlogFromExistingBlog(existing, this);
  713. if (!providerResult)
  714. return false;
  715. //if (Membership.Provider.Name.Equals("DbMembershipProvider", StringComparison.OrdinalIgnoreCase))
  716. //{
  717. //}
  718. //if (Roles.Provider.Name.Equals("DbRoleProvider", StringComparison.OrdinalIgnoreCase))
  719. //{
  720. //}
  721. return true;
  722. }
  723. internal bool DeleteBlogFolder()
  724. {
  725. // This method is called by the blog providers when a blog's storage container
  726. // is being deleted. Even the DbBlogProvider will call this method.
  727. // However, a different type of blog provider (e.g. Azure, etc) may not
  728. // need to call this method.
  729. try
  730. {
  731. string storagePath = HostingEnvironment.MapPath(this.StorageLocation);
  732. if (Directory.Exists(storagePath))
  733. {
  734. Directory.Delete(storagePath, true);
  735. }
  736. }
  737. catch (Exception ex)
  738. {
  739. Utils.Log("Blog.DeleteBlogFolder", ex);
  740. return false;
  741. }
  742. return true;
  743. }
  744. internal bool CopyExistingBlogFolderToNewBlogFolder(Blog existingBlog)
  745. {
  746. // This method is called by the blog providers when a new blog is being setup.
  747. // Even the DbBlogProvider will call this method. However, a different type of
  748. // blog provider (e.g. Azure, etc) may not need to call this method.
  749. if (string.IsNullOrWhiteSpace(this.StorageContainerName))
  750. throw new ArgumentException("this.StorageContainerName");
  751. string existingBlogStoragePath = null;
  752. try
  753. {
  754. // Ensure the existing blog storage path exists.
  755. existingBlogStoragePath = HostingEnvironment.MapPath(existingBlog.StorageLocation);
  756. if (!Directory.Exists(existingBlogStoragePath))
  757. {
  758. throw new Exception(string.Format("Storage folder for existing blog instance to copy from does not exist. Directory not found is: {0}", existingBlogStoragePath));
  759. }
  760. }
  761. catch (Exception ex)
  762. {
  763. Utils.Log("Blog.CreateNewBlogFromExisting", ex);
  764. throw; // re-throw error so error message bubbles up.
  765. }
  766. // Ensure "BlogInstancesFolderName" exists.
  767. string blogInstancesFolder = HostingEnvironment.MapPath(string.Format("{0}{1}", BlogConfig.StorageLocation, BlogConfig.BlogInstancesFolderName));
  768. if (!Utils.CreateDirectoryIfNotExists(blogInstancesFolder))
  769. return false;
  770. // If newBlogStoragePath already exists, throw an exception as this may be a mistake
  771. // and we don't want to overwrite any existing data.
  772. string newBlogStoragePath = HostingEnvironment.MapPath(this.StorageLocation);
  773. try
  774. {
  775. if (Directory.Exists(newBlogStoragePath))
  776. {
  777. throw new Exception(string.Format("Blog destination folder already exists. {0}", newBlogStoragePath));
  778. }
  779. }
  780. catch (Exception ex)
  781. {
  782. Utils.Log("Blog.CopyExistingBlogFolderToNewBlogFolder", ex);
  783. throw; // re-throw error so error message bubbles up.
  784. }
  785. if (!Utils.CreateDirectoryIfNotExists(newBlogStoragePath))
  786. return false;
  787. // Copy the entire directory contents.
  788. DirectoryInfo source = new DirectoryInfo(existingBlogStoragePath);
  789. DirectoryInfo target = new DirectoryInfo(newBlogStoragePath);
  790. try
  791. {
  792. // if the primary blog directly in App_Data is the 'source', when all the directories/files are
  793. // being copied to the new location, we don't want to copy the entire BlogInstancesFolderName
  794. // (by default ~/App_Data/blogs) to the new location. Everything except for that can be copied.
  795. // If the 'source' is a blog instance under ~/App_Data/blogs (e.g. ~/App_Data/blogs/template),
  796. // then this is not a concern.
  797. Utils.CopyDirectoryContents(source, target, new List<string>() { BlogConfig.BlogInstancesFolderName });
  798. }
  799. catch (Exception ex)
  800. {
  801. Utils.Log("Blog.CopyExistingBlogFolderToNewBlogFolder", ex);
  802. throw; // re-throw error so error message bubbles up.
  803. }
  804. return true;
  805. }
  806. public int CompareTo(Blog other)
  807. {
  808. // order so:
  809. // 1. active blogs come first
  810. // 2. blogs with longer Hostnames come first (length DESC)
  811. // 3. blogs not allowing any text before hostname come first.
  812. // 4. blogs with longer RelativeWebRoots come first (length DESC)
  813. // 5. blog name ASC.
  814. // it is sorted this way so the more specific criteria are evaluated first,
  815. // and pre-sorted to make CurrentInstance work as fast as possible.
  816. if (this.IsActive && !other.IsActive)
  817. return -1;
  818. else if (!this.IsActive && other.IsActive)
  819. return 1;
  820. int otherHostnameLength = other.Hostname.Length;
  821. int thisHostnameLength = this.hostname.Length;
  822. if (otherHostnameLength != thisHostnameLength)
  823. {
  824. return otherHostnameLength.CompareTo(thisHostnameLength);
  825. }
  826. // at this point, otherHostnameLength == thisHostnameLength.
  827. if (otherHostnameLength > 0) // if so, thisHostnameLength is also > 0.
  828. {
  829. if (this.IsAnyTextBeforeHostnameAccepted && !other.IsAnyTextBeforeHostnameAccepted)
  830. return 1;
  831. else if (!this.IsAnyTextBeforeHostnameAccepted && other.IsAnyTextBeforeHostnameAccepted)
  832. return -1;
  833. }
  834. int otherRelWebRootLength = other.RelativeWebRoot.Length;
  835. int thisRelWebRootLength = this.RelativeWebRoot.Length;
  836. if (otherRelWebRootLength != thisRelWebRootLength)
  837. {
  838. return otherRelWebRootLength.CompareTo(thisRelWebRootLength);
  839. }
  840. return this.Name.CompareTo(other.Name);
  841. }
  842. private CacheProvider _cache;
  843. /// <summary>
  844. /// blog instance cache
  845. /// </summary>
  846. public CacheProvider Cache
  847. {
  848. get { return _cache ?? (_cache = new CacheProvider(HttpContext.Current.Cache)); }
  849. }
  850. }
  851. }