In my previous post I discussed the Sitecore UploadWatcher — a Sitecore.IO.FileWatcher which monitors the /upload directory of your Sitecore instance and uploads files dropped into that directory into the Media Library.
One thing I could never find in the UploadWatcher is functionality to delete files in the /upload directory after they are uploaded into the Media Library — if you know of an “out of the box” way of doing this, please drop a comment.
After peeking into Sitecore.Resources.Media.UploadWatcher using .NET Reflector, I discovered I could add this functionality quite easily since its Created() method — this method handles the uploading of the files into the Media Library by delegating to Sitecore.Resources.Media.MediaManager.Creator.FileCreated() — is overridable. In theory, all I would need to do would be to subclass the UploadWatcher class; override the Created() method; delegate to the base class’ Created() method to handle the upload of the file to the Media Library; then delete the file once the base class’ Create() method was done executing.
After coding, experimenting and refactoring, I built the following class that subclasses Sitecore.Resources.Media.UploadWatcher:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; using Sitecore.Configuration; using Sitecore.Diagnostics; using Sitecore.IO; using Sitecore.Resources.Media; using Sitecore.Xml; namespace Sitecore.Sandbox.Resources.Media { public class CleanupUploadWatcher : UploadWatcher { private IEnumerable<string> PathsToIgnore { get; set; } private IEnumerable<string> FileNamePartsToIgnore { get; set; } private bool ShouldDeleteAfterUpload { get; set; } public CleanupUploadWatcher() : base() { Initialize(); } private void Initialize() { IEnumerable<XmlNode> configNodes = GetIgnoreConfigNodes(); PathsToIgnore = GetPathsToIgnore(configNodes); FileNamePartsToIgnore = GetFileNamePartsToIgnore(configNodes); ShouldDeleteAfterUpload = GetShouldDeleteAfterUpload(); } protected virtual IEnumerable<XmlNode> GetIgnoreConfigNodes() { string configNodeRoot = GetConfigNodeRoot(); if (string.IsNullOrWhiteSpace(configNodeRoot)) { return Enumerable.Empty<XmlNode>(); } XmlNode rootNode = Factory.GetConfigNode(configNodeRoot); if (rootNode == null) { return Enumerable.Empty<XmlNode>(); } return XmlUtil.GetChildNodes(rootNode, true); } protected virtual string GetConfigNodeRoot() { return "mediaLibrary/watcher/ignoreList"; } protected virtual IEnumerable<string> GetPathsToIgnore(IEnumerable<XmlNode> nodes) { if (IsEmpty(nodes)) { return Enumerable.Empty<string>(); } HashSet<string> pathsToIgnore = new HashSet<string>(); foreach (XmlNode node in nodes) { string containsValue = XmlUtil.GetAttribute("contains", node); if (ShouldAddContainsValue(containsValue, node, "ignorepath")) { pathsToIgnore.Add(containsValue); } } return pathsToIgnore; } protected virtual IEnumerable<string> GetFileNamePartsToIgnore(IEnumerable<XmlNode> nodes) { if (IsEmpty(nodes)) { return Enumerable.Empty<string>(); } HashSet<string> partsToIgnore = new HashSet<string>(); foreach (XmlNode node in nodes) { string containsValue = XmlUtil.GetAttribute("contains", node); if (ShouldAddContainsValue(containsValue, node, "ignore")) { partsToIgnore.Add(containsValue); } } return partsToIgnore; } protected virtual bool ShouldAddContainsValue(string containsValue, XmlNode node, string targetNodeName) { return !string.IsNullOrWhiteSpace(containsValue) && node != null && string.Equals(node.Name, targetNodeName, StringComparison.OrdinalIgnoreCase); } protected static bool IsEmpty<T>(IEnumerable<T> collection) { return collection == null || !collection.Any(); } protected override void Created(string filePath) { Assert.ArgumentNotNullOrEmpty(filePath, "filePath"); if (!ShouldIgnoreFile(filePath)) { base.Created(filePath); DeleteFile(filePath); } } protected virtual bool GetShouldDeleteAfterUpload() { XmlNode node = Factory.GetConfigNode("watchers/media/additionalSettings"); if (node == null) { return false; } string deleteAfterUploadValue = XmlUtil.GetAttribute("deleteAfterUpload", node); if(string.IsNullOrWhiteSpace(deleteAfterUploadValue)) { return false; } bool shouldDelete; bool.TryParse(deleteAfterUploadValue, out shouldDelete); return shouldDelete; } protected virtual bool ShouldIgnoreFile(string filePath) { Assert.ArgumentNotNullOrEmpty(filePath, "filePath"); foreach (string path in PathsToIgnore) { if (ContainsSubstring(filePath, path)) { return true; } } string fileName = Path.GetFileName(filePath); foreach (string part in FileNamePartsToIgnore) { if(ContainsSubstring(fileName, part)) { return true; } } return false; } protected virtual bool ContainsSubstring(string value, string substring) { if(string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(substring)) { return false; } return value.IndexOf(substring, StringComparison.OrdinalIgnoreCase) > -1; } protected virtual void DeleteFile(string filePath) { Assert.ArgumentNotNullOrEmpty(filePath, "filePath"); if(!ShouldDeleteAfterUpload) { return; } try { FileUtil.Delete(filePath); } catch(Exception ex) { Log.Error(ToString(), ex, this); } } } }
You might thinking “Mike, there is more going on in here than just delegating to the base class’ Created() method and deleting the file. Well, you are correct — I had to do a few things further than what I thought I needed to do, and I’ll explain why.
I had to duplicate the logic — actually I wrote my own logic — to parse the Sitecore configuration which defines the substrings of file names and paths to ignore since these collections on the base UploadWatcher class are private — subclasses cannot access these collections — in order to prevent the code from deleting a file that should be ignored.
I also wedged in configuration with code to turn off the delete functionality if needed.
I then created the following patch include configuration file to hold the configuration setting to turn the delete functionality on/off:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <watchers> <media> <additionalSettings deleteAfterUpload="true" /> </media> </watchers> </sitecore> </configuration>
I then registered the new CleanupUploadWatcher in the Web.config — please see my previous post which explains why this is needed:
<system.webServer> <modules runAllManagedModulesForAllRequests="true"> <!-- stuff here --> <!-- <add type="Sitecore.Resources.Media.UploadWatcher, Sitecore.Kernel" name="SitecoreUploadWatcher" /> --> <add type="Sitecore.Sandbox.Resources.Media.CleanupUploadWatcher, Sitecore.Sandbox" name="SitecoreUploadWatcher" /> <!-- more stuff down here --> </modules> <!-- and more stuff down here --> <system.webServer>
Let’s see this in action!
As you can see, there is no Media Library Item in the root of my Media Library (/sitecore/media library) — this is where the UploadWatcher uploads the file to:
I then copied an image from a folder on my Desktop to the /upload directory of my Sitecore instance:
As you can see above, the image is deleted after it is dropped.
I then went back to my Media Library and refreshed. As you can see here, the file was uploaded:
If you have any thoughts on this or suggestions on making it better, please share in a comment.