feanz
10/23/2013 - 10:28 AM

Sitecore deployment manageer

Sitecore deployment manageer

using Sitecore.Data.Managers;
using Sitecore.Data.Serialization;
using Sitecore.Diagnostics;
using Sitecore.Exceptions;
using Sitecore.Publishing;
using Sitecore.SecurityModel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Think.UKB.Deployment.Logging;

namespace Think.UKB.Deployment
{
    public class DeploymentManager
    {
        /// <summary>
        /// Lock to prevent serialization and deserialization to be running at same time.
        /// re-entrant locks will prevent a deadlock in the case of this called by the DeserializeFromZip and then DeserializeAll (etc)
        /// </summary>
        private static readonly object LockAll = new object();

        #region Properties

        /// <summary>
        /// Get the folder to read serialized content from. Defaults to SerializationFolder from Sitecore settings (web.config)
        /// For deserializing it must be SerializationFolder from Sitecore settings (web.config)
        /// </summary>
        public string SerializationRootFolder { get; protected set; }

        public ILogger Log { get; set; }

        private static LoadOptions _options;

        protected LoadOptions Options { get { return _options ?? (_options = new LoadOptions { DisableEvents = true }); } }

        #endregion Properties

        #region Constants

        protected const string WebDb = "web";
        protected const string MasterDb = "master";
        protected const string CoreDb = "core";

        protected const string RootItem = "sitecore";

        protected const string ContentRoot = RootItem + "\\content";
        protected const string LayoutRoot = RootItem + "\\layout";

        protected const string RenderingsRoot = LayoutRoot + "\\renderings";
        protected const string SublayoutsRoot = LayoutRoot + "\\sublayouts";

        protected const string SystemRoot = RootItem + "\\system";

        protected const string MediaRoot = RootItem + "\\media library";
        protected const string TemplateRoot = RootItem + "\\templates";

        #endregion Constants

        #region Constructor

        public DeploymentManager()
        {
            SerializationRootFolder = System.Web.HttpContext.Current.Server.MapPath(Sitecore.Configuration.Settings.SerializationFolder);
            Log = new HtmlLogger();
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="log">change the default logger</param>
        /// <param name="serializationRoot">use  a different root for serialization - warning Sitecore will fail if deserializing if it's not the SerializationFolder</param>
        public DeploymentManager(ILogger log, string serializationRoot)
        {
            SerializationRootFolder = serializationRoot;
            Log = log;
        }

        #endregion Constructor

        #region Deserialize

        /// <summary>
        /// Given a zip file, will unzip and load the serialized content - will clear all existing serialized content from the disk.
        /// </summary>
        /// <param name="zipFilePath"></param>
        public void DeserializeFromZipFile(string zipFilePath)
        {
            lock (LockAll)
            {
                var timer = GetTimer();
                var zipReader = new Sitecore.Zip.ZipReader(zipFilePath);

                //The serialization has to start from the web.config settings folder, otherwsie
                //the deserialize methods will throw exceptions about the folder location.
                var unzipPath = SerializationRootFolder;
                try
                {
                    if (Directory.Exists(unzipPath))
                        Directory.Delete(unzipPath, true);
                }
                catch (Exception e)
                {
                    Log.Error("Could not remove previous unzipped content. " + e.Message);
                    Log.Info("Derializing halted");
                    return;
                }
                foreach (var entry in zipReader.Entries)
                {
                    if (entry.IsDirectory)
                    {
                        Directory.CreateDirectory(Path.Combine(unzipPath, entry.Name));
                        continue;
                    }

                    var path = Path.GetDirectoryName(Path.Combine(unzipPath, entry.Name));
                    if (string.IsNullOrEmpty(path))
                        throw new Exception("Error getting path location during unzip.");

                    try
                    {
                        if (!Directory.Exists(path))
                        {
                            //Log.Info("Creating folder " + path);
                            Directory.CreateDirectory(path);
                        }
                    }
                    catch (Exception e)
                    {
                        Log.Error("Couldn't create path " + path + e.Message);
                    }
                    try
                    {
                        using (var stream = entry.GetStream())
                        using (var output = File.OpenWrite(Path.Combine(unzipPath, entry.Name)))
                        {
                            byte[] buffer = new byte[0x1000];
                            int read;
                            while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
                                output.Write(buffer, 0, read);
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Error("Error while unzipping content" + ex.Message);
                        return;
                    }
                }
                timer.Stop();
                Log.Info(string.Format("Unzip took {0} seconds", timer.Elapsed.TotalSeconds));
                DeserializeAll();
            }
        }

        /// <summary>
        /// Update everything from the file system.
        /// </summary>
        public void DeserializeAll()
        {
            lock (LockAll)
            {
                Log.Info("Deserialize All");
                var timer = GetTimer();

                try
                {
                    //disable the automatic serialization (in all threads) while this is running
                    ItemHandler.Disabled = true;

                    DeserializeMedia();

                    DeserialiseTemplates();

                    DeserializeLayouts();

                    DeserializeCoreDb();

                    DeserializeContent();
                }
                finally
                {
                    ItemHandler.Disabled = false;
                }

                timer.Stop();
                Log.Info(string.Format("Deserialize all took : {0} seconds", timer.Elapsed.TotalSeconds));
            }
        }

        /// <summary>
        /// Updates everything in core/sitecore
        /// </summary>
        public void DeserializeCoreDb()
        {
            lock (LockAll)
            {
                var contentPath = Path.Combine(new[] { SerializationRootFolder, CoreDb, RootItem });
                DeserialiseTree(contentPath);
            }
        }

        /// <summary>
        /// Updates master/sitecore/system recursively
        /// </summary>
        public void DeserializeSystemSettings()
        {
            lock (LockAll)
            {
                var contentPath = Path.Combine(new[] { SerializationRootFolder, MasterDb, SystemRoot });
                DeserialiseTree(contentPath);
            }
        }

        /// <summary>
        /// Updates master sitecore/layout recursively
        /// </summary>
        public void DeserializeLayouts()
        {
            lock (LockAll)
            {
                var contentPath = Path.Combine(new[] { SerializationRootFolder, MasterDb, LayoutRoot });
                DeserialiseTree(contentPath);
            }
        }

        /// <summary>
        /// Updates master/sitecore/content recursively
        /// </summary>
        public void DeserializeContent()
        {
            lock (LockAll)
            {
                var contentPath = Path.Combine(new[] { SerializationRootFolder, MasterDb, ContentRoot });
                DeserialiseTree(contentPath);
            }
        }

        /// <summary>
        /// Updates master/sitecore/layout/sublayouts
        /// </summary>
        public void DeserialiseSubLayouts()
        {
            lock (LockAll)
            {
                var sublayoutsPath = Path.Combine(new[] { SerializationRootFolder, MasterDb, SublayoutsRoot });
                DeserialiseTree(sublayoutsPath);
            }
        }

        /// <summary>
        /// Updates master/sitecore/layout/renderings recursively
        /// </summary>
        public void DeserialiseRenderings()
        {
            lock (LockAll)
            {
                var renderingPath = Path.Combine(new[] { SerializationRootFolder, MasterDb, RenderingsRoot });
                DeserialiseTree(renderingPath);
            }
        }

        /// <summary>
        /// Updates master/sitecore/media library recursively
        /// </summary>
        public void DeserializeMedia()
        {
            lock (LockAll)
            {
                var mediaItemsPath = Path.Combine(new[] { SerializationRootFolder, MasterDb, MediaRoot });
                DeserialiseTree(mediaItemsPath);
            }
        }

        /// <summary>
        /// Updates master/sitecore/Templates recursively
        /// </summary>
        public void DeserialiseTemplates()
        {
            lock (LockAll)
            {
                var templatesPath = Path.Combine(new[] { SerializationRootFolder, MasterDb, TemplateRoot });
                DeserialiseTree(templatesPath);
            }
        }

        /// <summary>
        /// Calls the sitecore load to deserialize and and create/update the tree with the items found
        /// </summary>
        /// <param name="contentPath"></param>
        private void DeserialiseTree(string contentPath)
        {
            Log.Debug("Getting all from " + contentPath);
            try
            {
                using (new SecurityDisabler())
                {
                    Manager.LoadTree(contentPath, Options);
                }
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
        }

        #endregion Deserialize

        #region Serialize

        /// <summary>
        /// Serialize All the things
        /// </summary>
        /// <returns></returns>
        public void SerializeAll()
        {
            lock (LockAll)
            {
                var timer = GetTimer();
                Log.Info("Serialize All");

                Manager.CleanupPath(SerializationRootFolder, true);
                SerializeContent();
                SerializeLayouts();
                SerializeMedia();
                SerializeTemplates();
                //SerializeCore();

                timer.Stop();
                Log.Info(string.Format("Serialize All took {0} seconds", timer.Elapsed.TotalSeconds));
            }
        }

        public void SerializeCore()
        {
            lock (LockAll)
            {
                Log.Info("Serialize Core");
                DumpTree(ContentRoot, CoreDb);
            }
        }

        /// <summary>
        /// Serialize the tree from /sitecore/content
        /// </summary>
        public void SerializeContent()
        {
            lock (LockAll)
            {
                Log.Info("Serialize Content");
                DumpTree(ContentRoot);
            }
        }

        /// <summary>
        /// Serailize layouts.
        /// </summary>
        public void SerializeLayouts()
        {
            lock (LockAll)
            {
                Log.Info("Serialize Layouts");
                DumpTree(LayoutRoot);
            }
        }

        /// <summary>
        /// Serialize Media items
        /// </summary>
        public void SerializeMedia()
        {
            lock (LockAll)
            {
                Log.Info("Serialize Media");
                DumpTree(MediaRoot);
            }
        }

        /// <summary>
        /// Serializes Templates section
        /// </summary>
        public void SerializeTemplates()
        {
            lock (LockAll)
            {
                Log.Info("Serialize Templates");
                DumpTree(TemplateRoot);
            }
        }

        /// <summary>
        /// Serialize the tree from the given (sitecore) path.  Assumes master DB if name not supplied.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="database"></param>
        protected void DumpTree(string path, string database = MasterDb)
        {
            if (string.IsNullOrEmpty(database))
                throw new InvalidValueException("Invalid database name");

            var db = Sitecore.Data.Database.GetDatabase(database);
            if (db == null)
                throw new InvalidValueException(string.Format("{0} in not a vaild database", database));

            var itemPath = ConvertToSitecorePath(path);

            //Need security disabled to allow access to core/other sections that may be restricted for anon access.
            using (new SecurityDisabler())
            {
                var item = db.GetItem(itemPath);
                if (item == null)
                {
                    Log.Warn(string.Format("Path '{0}' didn't return an item for db '{1}'", itemPath, database));
                    return;
                }
                Manager.DumpTree(item);
            }
        }

        /// <summary>
        /// Convert folder path to sitecore path (swap '\' with '/')
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private static string ConvertToSitecorePath(string path)
        {
            var itemPath = "/" + path.Replace("\\", "/");
            return itemPath;
        }

        private static Stopwatch GetTimer()
        {
            var timer = new Stopwatch();
            timer.Start();
            return timer;
        }

        /// <summary>
        /// Creates a new zip file of all currently serialized content.
        /// </summary>
        /// <returns>Full path to the zip file</returns>
        public string CreateZipFile()
        {
            lock (LockAll)
            {
                var timer = GetTimer();
                var zipFile = Path.Combine(SerializationRootFolder, "content.zip");

                try
                {
                    Log.Info("Remove previous zip file");
                    if (File.Exists(zipFile))
                    {
                        File.Delete(zipFile);
                    }
                }
                catch
                {
                    Log.Warn("Unable to delete previous zip file.");
                }

                using (var fileWriter = new Sitecore.Zip.ZipWriter(zipFile))
                {
                    var files = GetFiles(SerializationRootFolder, "*.item|*.user|*.role",
                        SearchOption.AllDirectories);
                    Log.Info(string.Format("Adding files ({0})", files.Count()));

                    var length = SerializationRootFolder.Length;
                    if (!SerializationRootFolder.EndsWith("\\"))
                        length += 1;

                    foreach (var file in files)
                    {
                        fileWriter.AddEntry(file.Remove(0, length), file);
                    }
                }

                timer.Stop();
                Log.Info(string.Format("Zip took {0} seconds", timer.Elapsed.TotalSeconds));
                return zipFile;
            }
        }

        /// <summary>
        /// Get all files based on search string supplied.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="searchPattern"></param>
        /// <param name="searchOption"></param>
        /// <returns></returns>
        protected static string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
        {
            string[] searchPatterns = searchPattern.Split('|');
            List<string> files = new List<string>();
            foreach (string sp in searchPatterns)
                files.AddRange(Directory.GetFiles(path, sp, searchOption));
            files.Sort();
            return files.ToArray();
        }

        #endregion Serialize

        #region Publish
        /// <summary>
        /// Perform incremental publish of all master -> web.
        /// </summary>
        /// <param name="targetDb">db name - eg "web", "web_cd" - default is "Web"</param>
        public void PublishIncremental(string targetDb = WebDb)
        {
            if (string.IsNullOrEmpty(targetDb)) throw new ArgumentNullException("targetDb");

            lock (LockAll)
            {
                Sitecore.Context.SetActiveSite("shell");
                using (new SecurityDisabler())
                {
                    Sitecore.Data.Database master = Sitecore.Configuration.Factory.GetDatabase(MasterDb);
                    Sitecore.Data.Database target = Sitecore.Configuration.Factory.GetDatabase(targetDb);

                    Assert.IsNotNull(master, "Could not find master database");
                    Assert.IsNotNull(target, string.Format("Could not find '{0}' database", targetDb));

                    PublishManager.PublishIncremental(master, new[] { target },
                                                        LanguageManager.GetLanguages(master).ToArray());
                }
            }
        }

        #endregion
    }
}
public partial class DeploymentController : Controller
	{
		private DeploymentManager _manager;

		protected DeploymentManager Manager
		{
			get { return _manager ?? (_manager = new DeploymentManager()); }
		}

		public virtual ActionResult Delete()
		{
			Manager.Logger = new HtmlLogger();
			Manager.DeleteAll();
			ViewBag.LogInfo = Manager.Logger.ToString();
			CheckForErrors();
			return View();
		}

		public string DeleteCatalogues()
		{
			Manager.DeleteCatalogues();
			return string.Format("Delete completed at {0}.", DateTime.Now.ToString("s"));
		}

		/// <summary>
		///     Deserialize what already is there (manual unzip/web deploy etc)
		/// </summary>
		/// <returns></returns>
		public virtual ActionResult DeserializeAll(bool cleanUp = false)
		{
			Manager.Logger = new HtmlLogger();
			Manager.DeserializeAll();

			if (cleanUp)
			{
				Manager.DeleteAll();
			}

			ViewBag.LogInfo = Manager.Logger.ToString();

			CheckForErrors();
			return View();
		}

		/// <summary>
		///     Deserialize what already is there (manual unzip/web deploy etc), Force full update ignoring local changes
		/// </summary>
		/// <returns></returns>
		public virtual ActionResult DeserializeAllForceUpdate()
		{
			Manager.Logger = new HtmlLogger();
			Manager.Options = new LoadOptions {DisableEvents = true, ForceUpdate = true};
			Manager.DeserializeAll();
			ViewBag.LogInfo = Manager.Logger.ToString();
			CheckForErrors();
			return View();
		}

		/// <summary>
		///     Present the form for uploading a file
		/// </summary>
		/// <returns></returns>
		public virtual ActionResult DeserializeFile()
		{
			ViewBag.SerializationFolder = Manager.SerializationRootFolder;
			CheckForErrors();
			return View();
		}

		/// <summary>
		///     Handle the uploaded file
		/// </summary>
		/// <param name="theFile"></param>
		/// <returns></returns>
		[HttpPost]
		public virtual ActionResult DeserializeFile(HttpPostedFileBase theFile)
		{
			if (theFile == null || theFile.ContentLength == 0 || !theFile.ContentType.Contains("zip"))
			{
				return RedirectToAction("DeserializeFile");
			}
			var rootPath = Server.MapPath("~/temp/deploy/");
			var upload = Path.Combine(rootPath, "content.zip");

			try
			{
				if (!Directory.Exists(rootPath))
					Directory.CreateDirectory(rootPath);

				if (System.IO.File.Exists(upload))
					System.IO.File.Delete(upload);
			}
			catch (Exception e)
			{
				Log.Error("Failed to remove previous uploaded file", e, this);
			}

			theFile.SaveAs(upload);

			Manager.DeserializeFromZipFile(upload);
			ViewBag.LogInfo = Manager.Logger.ToString();

			CheckForErrors();
			return View("DeserializeAll");
		}

		/// <summary>
		///     Deserialize what already is there (manual unzip/web deploy etc)
		/// </summary>
		/// <returns></returns>
		public string Publish()
		{
			Manager.Publish();
			return string.Format("Publish completed at {0}.", DateTime.Now.ToString("s"));
		}

		public virtual ActionResult SerializeAll()
		{
			ViewBag.File = SerializeAndZip();
			ViewBag.LogInfo = Manager.Logger.ToString();

			return View("SerializeAll");
		}

		/// <summary>
		///     Serializ(s)es all content and returns a zip file of it all.
		/// </summary>
		/// <returns></returns>
		public virtual FileResult SerializeAllFile()
		{
			var file = SerializeAndZip();
			ViewBag.File = file;

			var cd = new ContentDisposition
			{
				FileName = Path.GetFileName(file),
				Inline = false,
			};

			Response.AppendHeader("content-disposition", cd.ToString());
			return File(System.IO.File.OpenRead(file), "application/zip");
		}

		/// <summary>
		///     Deserialise and pubish site
		/// </summary>
		/// <returns></returns>
		public virtual ActionResult Update()
		{
			Manager.Logger = new HtmlLogger();
			Manager.DeserializeAll();

			Manager.Publish();

			Manager.Logger.Info("Smart Publish site content");

			ViewBag.LogInfo = Manager.Logger.ToString();

			return View();
		}

		private void CheckForErrors()
		{
			if (Manager.Logger.As<HtmlLogger>().ContainsErrors())
			{
				Response.StatusCode = 500;
			}
		}

		private string SerializeAndZip()
		{
			Manager.Logger = new HtmlLogger();
			Manager.SerializeAll();

			return Manager.CreateZipFile();
		}
	}