private async Task InternalDeleteFolder(
            DbContextScope scope,
            MediaFolder folder,
            TreeNode <MediaFolderNode> node,
            TreeNode <MediaFolderNode> root,
            FolderDeleteResult result,
            FileHandling strategy,
            CancellationToken cancelToken = default)
        {
            // (perf) We gonna check file tracks, so we should preload all tracks.
            await _db.LoadCollectionAsync(folder, (MediaFolder x) => x.Files, false, q => q.Include(f => f.Tracks));

            var files        = folder.Files.ToList();
            var lockedFiles  = new List <MediaFile>(files.Count);
            var trackedFiles = new List <MediaFile>(files.Count);

            // First delete files
            if (folder.Files.Any())
            {
                var albumId = strategy == FileHandling.MoveToRoot
                    ? _folderService.FindAlbum(folder.Id).Value.Id
                    : (int?)null;

                foreach (var batch in files.Slice(500))
                {
                    if (cancelToken.IsCancellationRequested)
                    {
                        break;
                    }

                    foreach (var file in batch)
                    {
                        if (cancelToken.IsCancellationRequested)
                        {
                            break;
                        }

                        if (strategy == FileHandling.Delete && file.Tracks.Any())
                        {
                            // Don't delete tracked files
                            trackedFiles.Add(file);
                            continue;
                        }

                        if (strategy == FileHandling.Delete)
                        {
                            try
                            {
                                result.DeletedFileNames.Add(file.Name);
                                await DeleteFileAsync(file, true);
                            }
                            catch (DeleteTrackedFileException)
                            {
                                trackedFiles.Add(file);
                            }
                            catch (IOException)
                            {
                                lockedFiles.Add(file);
                            }
                        }
                        else if (strategy == FileHandling.SoftDelete)
                        {
                            await DeleteFileAsync(file, false);

                            file.FolderId = null;
                            result.DeletedFileNames.Add(file.Name);
                        }
                        else if (strategy == FileHandling.MoveToRoot)
                        {
                            file.FolderId = albumId;
                            result.DeletedFileNames.Add(file.Name);
                        }
                    }

                    await scope.CommitAsync(cancelToken);
                }

                if (lockedFiles.Any())
                {
                    // Retry deletion of failed files due to locking.
                    // INFO: By default "LocalFileSystem" waits for 500ms until the lock is revoked or it throws.
                    foreach (var lockedFile in lockedFiles.ToArray())
                    {
                        if (cancelToken.IsCancellationRequested)
                        {
                            break;
                        }

                        try
                        {
                            await DeleteFileAsync(lockedFile, true);

                            lockedFiles.Remove(lockedFile);
                        }
                        catch { }
                    }

                    await scope.CommitAsync(cancelToken);
                }
            }

            if (!cancelToken.IsCancellationRequested && lockedFiles.Count > 0)
            {
                var fullPath = CombinePaths(root.Value.Path, lockedFiles[0].Name);
                throw new IOException(T("Admin.Media.Exception.InUse", fullPath));
            }

            if (!cancelToken.IsCancellationRequested && lockedFiles.Count == 0 && trackedFiles.Count == 0 && node.Children.All(x => result.DeletedFolderIds.Contains(x.Value.Id)))
            {
                // Don't delete folder if a containing file could not be deleted,
                // any tracked file was found or any of its child folders could not be deleted..
                _db.MediaFolders.Remove(folder);
                await scope.CommitAsync(cancelToken);

                result.DeletedFolderIds.Add(folder.Id);
            }

            result.LockedFileNames  = lockedFiles.Select(x => x.Name).ToList();
            result.TrackedFileNames = trackedFiles.Select(x => x.Name).ToList();
        }
        public async Task <MediaFolderInfo> CreateFolderAsync(string path)
        {
            Guard.NotEmpty(path, nameof(path));

            path = FolderService.NormalizePath(path, false);
            ValidateFolderPath(path, "CreateFolder", nameof(path));

            var dupe = _folderService.GetNodeByPath(path);

            if (dupe != null)
            {
                throw _exceptionFactory.DuplicateFolder(path, dupe.Value);
            }

            var  sep         = "/";
            var  folderNames = path.Split(new[] { sep }, StringSplitOptions.RemoveEmptyEntries);
            bool flag        = false;
            int  folderId    = 0;

            path = string.Empty;

            for (int i = 0; i < folderNames.Length; i++)
            {
                var folderName = MediaHelper.NormalizeFolderName(folderNames[i]);
                path += (i > 0 ? sep : string.Empty) + folderName;

                if (!flag)
                {
                    // Find the last existing node in path trail
                    var currentNode = _folderService.GetNodeByPath(path)?.Value;
                    if (currentNode != null)
                    {
                        folderId = currentNode.Id;
                    }
                    else
                    {
                        if (i == 0)
                        {
                            throw new NotSupportedException(T("Admin.Media.Exception.TopLevelAlbum", path));
                        }
                        flag = true;
                    }
                }

                if (flag)
                {
                    using (new DbContextScope(_db, deferCommit: false))
                    {
                        var mediaFolder = new MediaFolder {
                            Name = folderName, ParentId = folderId
                        };
                        _db.MediaFolders.Add(mediaFolder);
                        await _db.SaveChangesAsync();

                        folderId = mediaFolder.Id;
                    }
                }
            }

            return(new MediaFolderInfo(_folderService.GetNodeById(folderId)));
        }