PageRenderTime 39ms CodeModel.GetById 9ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/BlogEngine/DotNetSlave.BusinessLogic/Blog.cs

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