Last week a question was asked in one of the SDN forums on how one should go about deleting files on the filesystem that are associated with Items that are permanently deleted from the Recycle Bin — I wasn’t quite clear on what the original poster meant by files being linked to Items inside of Sitecore, but I assumed this relationship would be defined somewhere, or somehow.
After doing some research, I reckoned one could create a new command based on Sitecore.Shell.Framework.Commands.Archives.Delete in Sitecore.Kernel.dll to accomplish this:
However, I wasn’t completely satisfied with this approach, especially when it would require a substantial amount of copying and pasting of code — a practice that I vehemently abhor — and decided to seek out a different, if not better, way of doing this.
From my research, I discovered that one could just create his/her own Archive class — it would have to ultimately derive from Sitecore.Data.Archiving.Archive in Sitecore.Kernel — which would delete a file on the filesystem associated with a Sitecore Item:
using System; using System.IO; using System.Linq; using System.Web; using Sitecore.Configuration; using Sitecore.Data; using Sitecore.Data.Archiving; using Sitecore.Data.DataProviders.Sql; using Sitecore.Diagnostics; namespace Sitecore.Sandbox.Data.Archiving { public class FileSystemHookSqlArchive : SqlArchive { private static readonly string FolderPath = GetFolderPath(); public FileSystemHookSqlArchive(string name, Database database) : base(name, database) { } public override void RemoveEntries(ArchiveQuery query) { DeleteFromFileSystem(query); base.RemoveEntries(query); } protected virtual void DeleteFromFileSystem(ArchiveQuery query) { if (query.ArchivalId == Guid.Empty) { return; } Guid itemId = GetItemId(query.ArchivalId); if (itemId == Guid.Empty) { return; } string filePath = GetFilePath(itemId.ToString()); if (string.IsNullOrWhiteSpace(filePath)) { return; } TryDeleteFile(filePath); } private void TryDeleteFile(string filePath) { try { if (File.Exists(filePath)) { File.Delete(filePath); } } catch (Exception ex) { Log.Error(this.ToString(), ex, this); } } public virtual Guid GetItemId(Guid archivalId) { if (archivalId == Guid.Empty) { return Guid.Empty; } ArchiveQuery query = new ArchiveQuery { ArchivalId = archivalId }; SqlStatement selectStatement = GetSelectStatement(query, "{0}ItemId{1}"); if (selectStatement == null) { return Guid.Empty; } return GetGuid(selectStatement.Sql, selectStatement.GetParameters(), Guid.Empty); } private Guid GetGuid(string sql, object[] parameters, Guid defaultValue) { using (DataProviderReader reader = Api.CreateReader(sql, parameters)) { if (!reader.Read()) { return defaultValue; } return Api.GetGuid(0, reader); } } private static string GetFilePath(string fileName) { string filePath = Directory.GetFiles(FolderPath, string.Concat(fileName, "*.*")).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(filePath)) { return filePath; } return string.Empty; } private static string GetFolderPath() { return HttpContext.Current.Server.MapPath(Settings.GetSetting("FileSystemHookSqlArchive.Folder")); } } }
In the subclass of Sitecore.Data.Archiving.SqlArchive above — I’m using Sitecore.Data.Archiving.SqlArchive since I’m using SqlServer for my Sitecore instance — I try to find a file that is named after its associated Item’s ID — minus the curly braces — in a folder that I’ve mapped in a configuration include file (see below).
I first have to get the Item’s ID from the database using the supplied ArchivalId — this is all the calling code gives us, so we have to make do with what we have.
If the file exists, we try to delete it — we do this before letting the base class delete the Item from Recycle Bin so that we can retrieve the Item’s ID from the database before it’s removed from the Archive database table — and log any errors we encounter upon exception.
I then hooked in an instance of the above Archive class in a custom Sitecore.Data.Archiving.ArchiveProvider class:
using System.Xml; using Sitecore.Data; using Sitecore.Data.Archiving; using Sitecore.Xml; namespace Sitecore.Sandbox.Data.Archiving { public class FileSystemHookSqlArchiveProvider : SqlArchiveProvider { protected override Archive GetArchive(XmlNode configNode, Database database) { string attribute = XmlUtil.GetAttribute("name", configNode); if (string.IsNullOrEmpty(attribute)) { return null; } return new FileSystemHookSqlArchive(attribute, database); } } }
The above class — which derives from Sitecore.Data.Archiving.SqlArchiveProvider since I’m using SqlServer — only overrides its base class’s GetArchive factory method. We instantiate an instance of our Archive class instead of the “out of the box” Sitecore.Data.Archiving.SqlArchive class within it.
I then had to replace the “out of the box” Sitecore.Data.Archiving.ArchiveProvider reference, and define the location of our files in the following configuration file:
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <archives defaultProvider="sql" enabled="true"> <providers> <add name="sql" patch:instead="add[@type='Sitecore.Data.Archiving.SqlArchiveProvider, Sitecore.Kernel']" type="Sitecore.Sandbox.Data.Archiving.FileSystemHookSqlArchiveProvider, Sitecore.Sandbox" database="*"/> </providers> </archives> <settings> <setting name="FileSystemHookSqlArchive.Folder" value="/test/" /> </settings> </sitecore> </configuration>
Let’s test this out.
I first created a test Item to delete:
I then had to create a test file on the filesystem in my test folder — the test folder lives in my Sitecore instance’s website root:
I deleted the test Item from the content tree, opened up the Recycle Bin, selected the test Item, and got an itchy trigger finger — I want to delete the Item forever 🙂 :
After clicking the Delete button, I saw that the file on the filesystem was deleted as well:
If you have any thoughts on this, or recommendations around making it better, please leave a comment.