Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Media cache : A background task to purge all periodically. #8751

Closed
wants to merge 12 commits into from
11 changes: 11 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Media/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,14 @@
},
Category = "Content Management"
)]

[assembly: Feature(
Id = "OrchardCore.Media.Cache.BackgroundTask",
Name = "Media Cache Background Task",
Description = "Provides remote store cache and ImageSharp cache periodical purging ability.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Description = "Provides remote store cache and ImageSharp cache periodical purging ability.",
Description = "Provides a way to purge media cache regularly.",

Dependencies = new[]
{
"OrchardCore.Media.Cache"
},
Category = "Content Management"
)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using OrchardCore.BackgroundTasks;
using OrchardCore.Environment.Shell;
using OrchardCore.Media.Core;

namespace OrchardCore.Media.Services
{

// At 12:00 on Monday.
[BackgroundTask(Schedule = "0 12 * * 1", Description = "Performs cleanup operations for ms-cache and is-cache folders periodically.")]
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
// Left for test driving.
//[BackgroundTask(Schedule = "* * * * *", Description = "Performs cleanup operations for ms-cache and is-cache folders periodically.")]
public class MediaCacheBackgroundTask : IBackgroundTask
{
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly ShellSettings _shellSettings;
private readonly ILogger _logger;
private const int Repeats = 5;
private const int RepeatTime = 5000;

public MediaCacheBackgroundTask(
IWebHostEnvironment webHostEnvironment,
ShellSettings shellSettings,
ILogger<MediaCacheBackgroundTask> logger)
{
_webHostEnvironment = webHostEnvironment;
_shellSettings = shellSettings;
_logger = logger;
}

public Task DoWorkAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken)
{
_logger.LogInformation("Media cache background task cleaning started");

var directoryInfo = new DirectoryInfo(Path.Combine(_webHostEnvironment.WebRootPath, "is-cache"));
Copy link
Member

@MikeAlhayek MikeAlhayek Jan 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be using IMediaFileStoreCache service here by calling GetDirectoryContents("is-cache") instead of builder the DirectoryInfo manually?


// Don't delete is-cache folder.
RecursiveDeleteAsync(directoryInfo, false, cancellationToken);

directoryInfo = new DirectoryInfo(GetMediaCachePath(_webHostEnvironment, DefaultMediaFileStoreCacheFileProvider.AssetsCachePath, _shellSettings));
RecursiveDeleteAsync(directoryInfo, false, cancellationToken);
Copy link
Member

@MikeAlhayek MikeAlhayek Jan 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Skrypt

If the idea is to purge-all, you should use IMediaFileStoreCache and call PurgeAll() method which does the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have used that method if that would have worked all the time. The thing I'm trying to do here is to find out why it fails to delete some of these files because of a lock. I don't want to use another service that does that because I don't want to change the service itself. Once, we will have figured out the lock then we will update that service accordingly.


return Task.CompletedTask;
}

private async void RecursiveDeleteAsync(DirectoryInfo baseDir, bool deleteBaseDir, CancellationToken cancellationToken)
{
if (!baseDir.Exists)
{
return;
}

try
{
var dirs = baseDir.EnumerateDirectories().ToArray();
foreach (var dir in dirs)
{
try
{
RecursiveDeleteAsync(dir, true, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting dir {DirName}", dir.Name);
}
}
}
catch (Exception ee)
{
_logger.LogError(ee, "Error enumerating dirs {DirName}", baseDir.Name);
}

var files = baseDir.GetFiles();
foreach (var file in files)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
file.IsReadOnly = false;

if (IsFileLocked(file))
{
var i = 0;
while (file.Exists && IsFileLocked(file) && i <= Repeats)
{
await Task.Delay(RepeatTime, cancellationToken);
file.Delete();
i++;
}
}
else
{
file.Delete();
}
}
catch (Exception e)
{
_logger.LogError(e, "Error deleting cache file {FilePath}", file.Name);
}
}

if (deleteBaseDir)
{
try
{
var i = 0;
while (baseDir.Exists && baseDir.GetFiles().Length == 0 && i <= Repeats)
{
baseDir.Delete();
await Task.Delay(RepeatTime, cancellationToken);
i++;
}
}
catch (Exception e)
{
_logger.LogError(e, "Error deleting cache folder {DirectoryPath}", baseDir.Name);
}
}
}

private static bool IsFileLocked(FileInfo file)
{
FileStream stream = null;

try
{
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
// The file is unavailable because it is:
// still being written to
// or being processed by another thread
// or does not exist (has already been processed).
return true;
}
finally
{
if (stream != null)
{
stream.Close();
Skrypt marked this conversation as resolved.
Show resolved Hide resolved
}
}

//file is not locked
return false;
}

private static string GetMediaCachePath(IWebHostEnvironment hostingEnvironment, string assetsPath, ShellSettings shellSettings)
{
return PathExtensions.Combine(hostingEnvironment.WebRootPath, assetsPath, shellSettings.Name);
}
}
}
11 changes: 11 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Media/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrchardCore.Admin;
using OrchardCore.BackgroundTasks;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Display.ContentDisplay;
using OrchardCore.ContentManagement.Handlers;
Expand Down Expand Up @@ -342,6 +343,16 @@ public override void ConfigureServices(IServiceCollection services)
}
}

[Feature("OrchardCore.Media.Cache.BackgroundTask")]
public class MediaCacheBackgroundTaskStartup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
// Media cache background task
services.AddSingleton<IBackgroundTask, MediaCacheBackgroundTask>();
}
}

[Feature("OrchardCore.Media.Slugify")]
public class MediaSlugifyStartup : StartupBase
{
Expand Down