using System; using System.IO; using System.Linq; using System.Xml; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { class PreviewContent { private readonly int _userId; private readonly Guid _previewSet; private string _previewSetPath; private XmlDocument _previewXml; private readonly XmlStore _xmlStore; /// /// Gets the XML document. /// /// May return null if the preview content set is invalid. public XmlDocument XmlContent { get { // null if invalid preview content if (_previewSetPath == null) return null; // load if not loaded yet if (_previewXml != null) return _previewXml; _previewXml = new XmlDocument(); try { _previewXml.Load(_previewSetPath); } catch (Exception ex) { Current.Logger.Error($"Could not load preview set {_previewSet} for user {_userId}.", ex); ClearPreviewSet(); _previewXml = null; _previewSetPath = null; // do not try again } return _previewXml; } } /// /// Gets the preview token. /// /// To be stored in a cookie or wherever appropriate. public string Token => _userId + ":" + _previewSet; /// /// Initializes a new instance of the class for a user. /// /// The underlying Xml store. /// The user identifier. public PreviewContent(XmlStore xmlStore, int userId) { if (xmlStore == null) throw new ArgumentNullException(nameof(xmlStore)); _xmlStore = xmlStore; _userId = userId; _previewSet = Guid.NewGuid(); _previewSetPath = GetPreviewSetPath(_userId, _previewSet); } /// /// Initializes a new instance of the with a preview token. /// /// The underlying Xml store. /// The preview token. public PreviewContent(XmlStore xmlStore, string token) { if (xmlStore == null) throw new ArgumentNullException(nameof(xmlStore)); _xmlStore = xmlStore; if (token.IsNullOrWhiteSpace()) throw new ArgumentException("Null or empty token.", nameof(token)); var parts = token.Split(':'); if (parts.Length != 2) throw new ArgumentException("Invalid token.", nameof(token)); if (int.TryParse(parts[0], out _userId) == false) throw new ArgumentException("Invalid token.", nameof(token)); if (Guid.TryParse(parts[1], out _previewSet) == false) throw new ArgumentException("Invalid token.", nameof(token)); _previewSetPath = GetPreviewSetPath(_userId, _previewSet); } // creates and saves a new preview set // used in 2 places and each time includeSubs is true // have to use the Document class at the moment because IContent does not do ToXml... public void CreatePreviewSet(int contentId, bool includeSubs) { // note: always include subs _previewXml = _xmlStore.GetPreviewXml(contentId, includeSubs); // make sure the preview folder exists var dir = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Preview)); if (dir.Exists == false) dir.Create(); // clean old preview sets ClearPreviewDirectory(_userId, dir); // save _previewXml.Save(_previewSetPath); } // get the full path to the preview set private static string GetPreviewSetPath(int userId, Guid previewSet) { return IOHelper.MapPath(Path.Combine(SystemDirectories.Preview, userId + "_" + previewSet + ".config")); } // deletes files for the user, and files accessed more than one hour ago private static void ClearPreviewDirectory(int userId, DirectoryInfo dir) { var now = DateTime.Now; var prefix = userId + "_"; foreach (var file in dir.GetFiles("*.config") .Where(x => x.Name.StartsWith(prefix) || (now - x.LastAccessTime).TotalMinutes > 1)) { DeletePreviewSetFile(userId, file); } } // delete one preview set file in a safe way private static void DeletePreviewSetFile(int userId, FileSystemInfo file) { try { file.Delete(); } catch (Exception ex) { Current.Logger.Error($"Couldn't delete preview set {file.Name} for user {userId}", ex); } } /// /// Deletes the preview set in a safe way. /// public void ClearPreviewSet() { if (_previewSetPath == null) return; var previewSetFile = new FileInfo(_previewSetPath); DeletePreviewSetFile(_userId, previewSetFile); } } }