diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index 8a0fdaf290..df13363b95 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Events { - public class DeleteEventArgs : CancellableObjectEventArgs>, IEquatable> + public class DeleteEventArgs : CancellableObjectEventArgs>, IEquatable>, IDeletingMediaFilesEventArgs { /// /// Constructor accepting multiple entities that are used in the delete operation diff --git a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs new file mode 100644 index 0000000000..45681042ba --- /dev/null +++ b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Events +{ + internal interface IDeletingMediaFilesEventArgs + { + List MediaFilesToDelete { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index c6049cb7a6..c6d7c659b7 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Events { - public class RecycleBinEventArgs : CancellableEventArgs, IEquatable + public class RecycleBinEventArgs : CancellableEventArgs, IEquatable, IDeletingMediaFilesEventArgs { public RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData, bool emptiedSuccessfully) : base(false) @@ -97,6 +97,8 @@ namespace Umbraco.Core.Events /// public List Files { get; private set; } + public List MediaFilesToDelete { get { return Files; } } + /// /// Gets the list of all property data associated with a content id /// diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs index 6eb6ee3b85..31331a2d8d 100644 --- a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs +++ b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Umbraco.Core.IO; namespace Umbraco.Core.Events { @@ -117,9 +118,21 @@ namespace Umbraco.Core.Events if (_events == null) return; + var mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem; + if (RaiseEvents && completed) + { foreach (var e in _events) + { e.RaiseEvent(); + + // fixme - not sure I like doing it here - but then where? how? + var delete = e.Args as IDeletingMediaFilesEventArgs; + if (delete != null && delete.MediaFilesToDelete.Count > 0) + mediaFileSystem.DeleteMediaFiles(delete.MediaFilesToDelete); + } + } + _events.Clear(); } } diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index 6f32ef6da0..d9281a7590 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -24,6 +25,7 @@ namespace Umbraco.Core.IO { private readonly IContentSection _contentConfig; private readonly UploadAutoFillProperties _uploadAutoFillProperties; + private readonly ILogger _logger; private readonly object _folderCounterLock = new object(); private long _folderCounter; @@ -42,6 +44,7 @@ namespace Umbraco.Core.IO public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger) : base(wrapped) { + _logger = logger; _contentConfig = contentConfig; _uploadAutoFillProperties = new UploadAutoFillProperties(this, logger, contentConfig); } @@ -99,7 +102,7 @@ namespace Umbraco.Core.IO } else { - // new scheme: path is "-/" OR "--" + // new scheme: path is "/" where xuid is a combination of cuid and puid // default media filesystem maps to "~/media/" // assumes that cuid and puid keys can be trusted - and that a single property type // for a single content cannot store two different files with the same name @@ -435,6 +438,64 @@ namespace Umbraco.Core.IO } } + public void DeleteMediaFiles(IEnumerable files) + { + files = files.Distinct(); + + Parallel.ForEach(files, file => + { + try + { + if (file.IsNullOrWhiteSpace()) return; + + if (FileExists(file) == false) return; + DeleteFile(file, true); + + if (UseTheNewMediaPathScheme == false) + { + // old scheme: filepath is "/" OR "-" + // remove the directory if any + var dir = Path.GetDirectoryName(file); + if (string.IsNullOrWhiteSpace(dir) == false) + DeleteDirectory(dir, true); + } + else + { + // new scheme: path is "/" where xuid is a combination of cuid and puid + // remove the directory + var dir = Path.GetDirectoryName(file); + DeleteDirectory(dir, true); + } + + // I don't even understand... + /* + + var relativeFilePath = GetRelativePath(file); // fixme - should be relative already + if (FileExists(relativeFilePath) == false) return; + + var parentDirectory = Path.GetDirectoryName(relativeFilePath); + + // don't want to delete the media folder if not using directories. + if (_contentSection.UploadAllowDirectories && parentDirectory != GetRelativePath("/")) + { + //issue U4-771: if there is a parent directory the recursive parameter should be true + DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false); + } + else + { + DeleteFile(file, true); + } + + */ + } + catch (Exception e) + { + _logger.Error("Failed to delete attached file \"" + file + "\".", e); + } + }); + } + + #endregion #region GenerateThumbnails diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs index 005c1d62ba..8a5a99b32a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDeleteMediaFilesRepository.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Persistence.Repositories { + // cannot kill in v7 because it is public, kill in v8 + [Obsolete("Use MediaFileSystem.DeleteMediaFiles instead.", false)] public interface IDeleteMediaFilesRepository { /// @@ -9,6 +12,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// + [Obsolete("Use MediaFileSystem.DeleteMediaFiles instead.", false)] bool DeleteMediaFiles(IEnumerable files); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index af06c7e0ba..2455a53f55 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -179,7 +179,7 @@ namespace Umbraco.Core.Services uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parentId)); //Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this, uow.Events); - // fixme + // fixme var auditRepo = RepositoryFactory.CreateAuditRepository(uow); auditRepo.AddOrUpdate(new AuditItem(content.Id, string.Format("Content '{0}' was created", name), AuditType.New, content.CreatorId)); uow.Commit(); @@ -1051,9 +1051,9 @@ namespace Umbraco.Core.Services UnPublish(content, userId); } content.WriterId = userId; - content.ChangeTrashedState(true); + content.ChangeTrashedState(true); repository.AddOrUpdate(content); - + //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId foreach (var descendant in descendants) { @@ -1063,14 +1063,14 @@ namespace Umbraco.Core.Services descendant.ChangeTrashedState(true, descendant.ParentId); repository.AddOrUpdate(descendant); - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); } uow.Commit(); uow.Events.Dispatch(Trashed, this, new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), "Trashed"); } - + Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); return OperationStatus.Success(evtMsgs); @@ -1285,9 +1285,6 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(content, false, evtMsgs); uow.Events.Dispatch(Deleted, this, args, "Deleted"); // fixme why the event name?! - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); } Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); @@ -1335,7 +1332,7 @@ namespace Umbraco.Core.Services /// /// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. - /// + /// /// Id of the /// Optional Id of the user issueing the delete operation public void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0) @@ -1344,7 +1341,7 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.GetUnitOfWork()) { var repository = RepositoryFactory.CreateContentRepository(uow); - + //track the 'root' items of the collection of nodes discovered to delete, we need to use //these items to lookup descendants that are not of this doc type so they can be transfered //to the recycle bin @@ -1559,9 +1556,6 @@ namespace Umbraco.Core.Services success = repository.EmptyRecycleBin(); - if (success) - repository.DeleteMediaFiles(files); - uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files, success)); uow.Commit(); } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 9f5ec811c9..08494a3662 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -831,9 +831,6 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(media, false, evtMsgs); uow.Events.Dispatch(Deleted, this, args); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); } Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); @@ -958,9 +955,6 @@ namespace Umbraco.Core.Services success = repository.EmptyRecycleBin(); // FIXME shouldn't we commit here?! uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files, success)); - - if (success) - repository.DeleteMediaFiles(files); } } Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21); diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 7c7afdd2a6..841d79f687 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -45,7 +45,7 @@ namespace Umbraco.Core.Services /// /// Gets the default MemberType alias /// - /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll + /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll /// return the first type that is not an admin, otherwise if there's only one we will return that one. /// Alias of the default MemberType public string GetDefaultMemberType() @@ -86,7 +86,7 @@ namespace Umbraco.Core.Services /// /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method /// - /// This method exists so that Umbraco developers can use one entry point to create/update + /// This method exists so that Umbraco developers can use one entry point to create/update /// Members if they choose to. /// The Member to save the password for /// The password to encrypt and save @@ -776,7 +776,7 @@ namespace Umbraco.Core.Services /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -792,7 +792,7 @@ namespace Umbraco.Core.Services /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -806,7 +806,7 @@ namespace Umbraco.Core.Services /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -836,7 +836,7 @@ namespace Umbraco.Core.Services /// /// Creates and persists a Member /// - /// Using this method will persist the Member object before its returned + /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create @@ -921,7 +921,7 @@ namespace Umbraco.Core.Services /// public IMember GetByUsername(string username) { - //TODO: Somewhere in here, whether at this level or the repository level, we need to add + //TODO: Somewhere in here, whether at this level or the repository level, we need to add // a caching mechanism since this method is used by all the membership providers and could be // called quite a bit when dealing with members. @@ -953,9 +953,6 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(member, false); uow.Events.Dispatch(Deleted, this, args); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); } } @@ -963,7 +960,7 @@ namespace Umbraco.Core.Services /// Saves an /// /// to Save - /// Optional parameter to raise events. + /// Optional parameter to raise events. /// Default is True otherwise set to False to not raise events public void Save(IMember entity, bool raiseEvents = true) { @@ -1004,7 +1001,7 @@ namespace Umbraco.Core.Services /// Saves a list of objects /// /// to save - /// Optional parameter to raise events. + /// Optional parameter to raise events. /// Default is True otherwise set to False to not raise events public void Save(IEnumerable entities, bool raiseEvents = true) { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 94267cf21f..8565144dee 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -313,6 +313,7 @@ +