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

Create asset modified cleanup handler #856

Closed
wants to merge 69 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
e6e59c1
Ensure ImageDeliveryChannels are ordered in API results
donaldgray Jun 5, 2024
751a7dd
Small tweaks in API asset processing classes
donaldgray Jun 5, 2024
5e46840
Merge pull request #862 from dlcs/feature/delivery_channel_order
donaldgray Jun 5, 2024
a639017
Merge pull request #864 from dlcs/develop
donaldgray Jun 12, 2024
76ac631
Initial commit adding ailty to use a stickiness cookie when generatin…
JackLewis-digirati Jun 18, 2024
e8c9ebf
remove unnecessary using
JackLewis-digirati Jun 18, 2024
9323a10
adding additional test
JackLewis-digirati Jun 19, 2024
51d6465
removing expired from test to avoid issues after cookie expires
JackLewis-digirati Jun 20, 2024
5223e92
Do not include ImageService3 on v2 manifests
donaldgray Jun 24, 2024
7856018
Merge pull request #867 from dlcs/feature/presi2_updates
donaldgray Jun 24, 2024
ee3f39d
Allow batch with empty 'members' in legacy mode
donaldgray Jun 26, 2024
5aa8eeb
Merge pull request #871 from dlcs/feature/empty_members
donaldgray Jun 26, 2024
5748fa1
iniitial commit adding support for optional named queries + tightenin…
JackLewis-digirati Jun 26, 2024
612ae9c
getting named queries to work core4ctly with empty values and removin…
JackLewis-digirati Jun 27, 2024
5ec5593
update to remove unneeded test + method parameter + fixing orchestrat…
JackLewis-digirati Jun 27, 2024
5d2137d
remove unneeded using + rename test to be more accurate
JackLewis-digirati Jun 27, 2024
a4205dc
Omit '@Type' for ImageService in Presentation2 manifests
griffri Jun 27, 2024
c6522c9
Update iiif-net to 0.2.5
griffri Jun 27, 2024
8ae55ac
Update AngleSharp to 0.17.1 for Portal.Tests and Orchestrator.Tests
griffri Jun 27, 2024
e9aacf1
Use if instead of ternary to set ImageService2 type
griffri Jun 27, 2024
d274977
Use 'iiif:Image' @Type in ImageService2 for info.json
griffri Jun 27, 2024
184018e
Update InfoJsonBuilderTests
griffri Jun 27, 2024
564ee53
using long instead of int for converting to a number
JackLewis-digirati Jun 27, 2024
f458a03
Merge pull request #872 from dlcs/feature/optionalNamedQuery
JackLewis-digirati Jun 27, 2024
903026e
Set type of imageService2 in InfoJsonService
griffri Jun 27, 2024
3c738a4
Merge pull request #873 from dlcs/fix/omit_type_p2manifest
donaldgray Jun 27, 2024
4e6efea
Add basic named query viewer
griffri Jun 28, 2024
bfead48
Implement ability to create, update and delete named queries
griffri Jun 28, 2024
a5fd3fa
Add "Create new named query" placeholder, reposition "add" button
griffri Jun 28, 2024
bc3dfe8
Require template and name on NQ creation
griffri Jun 28, 2024
919449a
Reposition delete modal buttons
griffri Jun 28, 2024
9305937
Add success/failure messages on creating NQ, refactor
griffri Jun 28, 2024
5b87589
Use FromQuery instead of FromRoute
griffri Jun 28, 2024
145104b
Update DeleteNamedQuery and UpdateNamedQuery to log errors
griffri Jul 1, 2024
6b67b65
Rewrite create/update named query forms to return errors from API
griffri Jul 1, 2024
a1b62e2
Cleanup
griffri Jul 1, 2024
49fbb77
Merge pull request #875 from dlcs/feature/portal_nq_management
griffri Jul 1, 2024
73013e3
Request V3 Image API when retrieving thumbnails
griffri Jul 1, 2024
aa2f475
Merge pull request #878 from dlcs/fix/thumbs_viewer_uses_v3
griffri Jul 1, 2024
877ee4c
code review comments to rely on request headers + add cookie whitelist
JackLewis-digirati Jul 2, 2024
a5721ad
code review comments to swap to new way retrieving asset ids + fix to…
JackLewis-digirati Jul 2, 2024
e591d79
update to remove saving the cookie and to check the string sdtarts wi…
JackLewis-digirati Jul 3, 2024
82f5a87
update to use = in cookie name check
JackLewis-digirati Jul 4, 2024
b02da2c
adding back saving of cookie
JackLewis-digirati Jul 4, 2024
50680d5
removing cookie check
JackLewis-digirati Jul 4, 2024
58f3e3d
remove cookies from cantaloupe http client factory
JackLewis-digirati Jul 5, 2024
cc10b74
update to use header names
JackLewis-digirati Jul 5, 2024
8be76cd
Merge pull request #866 from dlcs/feature/cantaloupeStickiness
JackLewis-digirati Jul 9, 2024
0e7dc19
adding logic for deciding if work needs to be done on asset modified
JackLewis-digirati May 7, 2024
a187054
updates to modify asset cleanup on removal of DC + issues identified …
JackLewis-digirati May 8, 2024
6e39d93
update to put messages on queue
JackLewis-digirati May 15, 2024
8b89ba3
modify topic publisher to be scoped in API
JackLewis-digirati May 15, 2024
daec731
fixes for existing asset being updated by ref
JackLewis-digirati May 16, 2024
27049e6
updates to closer match modified assets to changes
JackLewis-digirati May 16, 2024
7d74eb7
working to get changed assets working correctly
JackLewis-digirati May 17, 2024
eec0d00
test boilerplate for asset modified cleanup
JackLewis-digirati May 20, 2024
a3c5f70
adding first set of tests for asset modified handler
JackLewis-digirati May 20, 2024
dd44811
adding tests for modified and updated
JackLewis-digirati May 21, 2024
1ed157d
updates to asset modified handler to add roles changes
JackLewis-digirati May 21, 2024
7f9b7b0
adding changes to support removal of legacy thumbnails in the asset m…
JackLewis-digirati May 22, 2024
d96dfdc
changes to remove unneeded code in the asset modified handler + furth…
JackLewis-digirati May 22, 2024
b9af78c
refactoring transcoder + adding some tests
JackLewis-digirati May 22, 2024
62d42f2
inital set of code review changes for asset modified cleanup
JackLewis-digirati May 28, 2024
3c328e2
adding second set of fixes for asset modfiied cleanup
JackLewis-digirati May 29, 2024
5827289
adding engine client tests
JackLewis-digirati May 29, 2024
88d6e8b
adding asset clone test
JackLewis-digirati May 29, 2024
1987ed9
further code review comment fixes for asset modified cleanup
JackLewis-digirati Jun 3, 2024
3f5b510
update to have engine failures return false in the asset updated
JackLewis-digirati Jun 3, 2024
447c314
adding caching to appsettings
JackLewis-digirati Jun 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/rfcs/017-asset-modified-cleanup.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,16 @@ This is that the delivery channel stays the same, but the id of the policy has c

- iiif-img changed
- `info.json` needs removed
- If it moves to a `use-original` policy, the derivative asset can be removed
- If it moves away from `use-original`, then the `/original` asset can also be removed, provided there isn't a `file` channel
- Thumbs changed
- Thumbs need to be removed that are no longer required.
- `s.json` and asset application metadata should be updated - `s.json` should be updated by the reingest
- `info.json` removed
- iiif-av changed
- Old transcode derivative removed if the file extension is no longer required
- File changed
- The asset at origin should be removed if there's an asset on the `/original` path - should only be removed if `iiif-img` is not using it
- do nothing

#### Roles changed

Expand All @@ -126,15 +128,15 @@ The policy data being updated can be found from the date that the delivery chann
- iiif-img changed
- `info.json` needs removed
- If it moves to a `use-original` policy, the derivative asset can be removed
- If it moves awa from `use-original`, then the `/original` asset can also be removed, provided there isn't a `file` channel
- If it moves away from `use-original`, then the `/original` asset can also be removed, provided there isn't a `file` channel
- Thumbs changed
- Thumbs need to be removed that are no longer required.
- `s.json` and asset application metadata should be updated - `s.json` should be updated by the reingest
- `info.json` removed
- iiif-av changed
- Old transcode derivative removed if the file extension is no longer required
- File changed
- The asset at origin should be removed if there's an asset on the `/original` path - should only be removed if `iiif-img` is not using it
- do nothing

## General comments

Expand Down
48 changes: 45 additions & 3 deletions src/protagonist/API.Client/DlcsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public async Task<HydraCollection<Image>> GetSpaceImages(int page, int pageSize,
var response = await httpClient.GetAsync(url);
return await response.ReadAsHydraResponseAsync<CustomerStorage>(jsonSerializerSettings);
}
catch (Exception ex)
catch (Exception)
{
logger.LogError("Failed to deserialize storage for space {SpaceStorage}", spaceId);
return null;
Expand Down Expand Up @@ -196,7 +196,7 @@ public async Task<bool> DeletePortalUser(string portalUserId)
var url = $"customers/{currentUser.GetCustomerId()}/portalUsers/{portalUserId}";
try
{
var response = await httpClient.DeleteAsync(url);
await httpClient.DeleteAsync(url);
return true;
}
catch (Exception ex)
Expand Down Expand Up @@ -226,7 +226,7 @@ public async Task<bool> DeleteImage(int spaceId, string imageId)
var uri = $"customers/{currentUser.GetCustomerId()}/spaces/{spaceId}/images/{imageId}";
try
{
var response = await httpClient.DeleteAsync(uri);
await httpClient.DeleteAsync(uri);
return true;
}
catch (Exception ex)
Expand Down Expand Up @@ -317,7 +317,49 @@ public async Task<CustomerQueue> GetQueue()
var queue = await response.ReadAsHydraResponseAsync<CustomerQueue>(jsonSerializerSettings);
return queue;
}

public async Task<IEnumerable<NamedQuery>> GetNamedQueries(bool includeGlobal)
{
var url = $"customers/{currentUser.GetCustomerId()}/namedQueries";
var response = await httpClient.GetAsync(url);
var namedQueries = await response.ReadAsHydraResponseAsync<HydraCollection<NamedQuery>>(jsonSerializerSettings);

return includeGlobal
? namedQueries.Members
: namedQueries.Members.Where(nq => nq.Global == false);
}

public async Task<bool> DeleteNamedQuery(string namedQueryId)
{
var url = $"customers/{currentUser.GetCustomerId()}/namedQueries/{namedQueryId}";
try
{
await httpClient.DeleteAsync(url);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Error deleting named query '{NamedQueryId}'", namedQueryId);
return false;
}
}

public async Task<NamedQuery?> UpdateNamedQuery(string namedQueryId, string template)
{
var url = $"customers/{currentUser.GetCustomerId()}/namedQueries/{namedQueryId}";
var response = await httpClient.PutAsync(url, ApiBody(new NamedQuery(){ Template = template }));
var updatedNamedQuery = await response.ReadAsHydraResponseAsync<NamedQuery>(jsonSerializerSettings);
return updatedNamedQuery;
}

public async Task<NamedQuery> CreateNamedQuery(NamedQuery newNamedQuery)
{
var url = $"customers/{currentUser.GetCustomerId()}/namedQueries";
var response = await httpClient.PostAsync(url, ApiBody(newNamedQuery));
var namedQuery = await response.ReadAsHydraResponseAsync<NamedQuery>(jsonSerializerSettings);
return namedQuery;
}

private HttpContent ApiBody(JsonLdBase apiObject)
{
var jsonString = JsonConvert.SerializeObject(apiObject, jsonSerializerSettings);
Expand Down
4 changes: 4 additions & 0 deletions src/protagonist/API.Client/IDlcsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ Task<HydraCollection<Image>> GetSpaceImages(int page, int pageSize, int spaceId,
Task<bool> TestBatch(int batchId);
Task<HydraCollection<Image>> GetBatchImages(int batchId, int page, int pageSize);
Task<CustomerQueue> GetQueue();
Task<IEnumerable<NamedQuery>> GetNamedQueries(bool includeGlobal);
Task<bool> DeleteNamedQuery(string namedQueryId);
Task<NamedQuery?> UpdateNamedQuery(string namedQueryId, string template);
Task<NamedQuery> CreateNamedQuery(NamedQuery newNamedQuery);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,22 @@ public void Create_SetsCorrectFields()
notification.Before.Should().BeNull();
}

[Fact]
public void Update_SetsCorrectFields()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Update_SetsCorrectFields(bool engineNotified)
{
// Arrange
var before = new Asset { Id = new AssetId(1, 2, "foo") };
var after = new Asset { Id = new AssetId(1, 2, "foo"), MaxUnauthorised = 10 };

// Act
var notification = AssetModificationRecord.Update(before, after);
var notification = AssetModificationRecord.Update(before, after, engineNotified);

// Assert
notification.ChangeType.Should().Be(ChangeType.Update);
notification.Before.Should().Be(before);
notification.After.Should().Be(after);
notification.EngineNotified.Should().Be(engineNotified);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using API.Infrastructure.Messaging;
using DLCS.AWS.SNS;
using DLCS.Core.Types;
Expand Down Expand Up @@ -33,7 +32,7 @@ public async Task SendAssetModifiedMessage_Single_SendsNotification_IfUpdate()
{
// Arrange
var assetModifiedRecord =
AssetModificationRecord.Update(new Asset(new AssetId(1, 2, "foo")), new Asset(new AssetId(1, 2, "bar")));
AssetModificationRecord.Update(new Asset(new AssetId(1, 2, "foo")), new Asset(new AssetId(1, 2, "bar")), true);

// Act
await sut.SendAssetModifiedMessage(assetModifiedRecord, CancellationToken.None);
Expand Down Expand Up @@ -76,7 +75,7 @@ public async Task SendAssetModifiedMessage_Single_SendsNotification_IfDelete()
A.CallTo(() =>
topicPublisher.PublishToAssetModifiedTopic(
A<IReadOnlyList<AssetModifiedNotification>>.That.Matches(n =>
n.Single().ChangeType == ChangeType.Delete && n.Single().MessageContents.Contains(customerName)),
n.Single().Attributes.Values.Contains(ChangeType.Delete.ToString()) && n.Single().MessageContents.Contains(customerName)),
A<CancellationToken>._)).MustHaveHappened();
}

Expand All @@ -102,7 +101,7 @@ public async Task SendAssetModifiedMessage_Multiple_SendsNotification_IfDelete()
topicPublisher.PublishToAssetModifiedTopic(
A<IReadOnlyList<AssetModifiedNotification>>.That.Matches(n =>
n.Count == 2 && n.All(m =>
m.ChangeType == ChangeType.Delete && m.MessageContents.Contains(customerName))),
n.First().Attributes.Values.Contains(ChangeType.Delete.ToString()) && m.MessageContents.Contains(customerName))),
A<CancellationToken>._)).MustHaveHappened();
}
}
44 changes: 44 additions & 0 deletions src/protagonist/API.Tests/Integration/CustomerQueueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,30 @@ public async Task Post_CreateBatch_201_IfLegacyModeEnabled()
response.StatusCode.Should().Be(HttpStatusCode.Created);
}

[Fact]
public async Task Post_CreateBatch_201_IfLegacyModeEnabled_MembersEmpty()
{
// Arrange
var hydraImageBody = @"{
""@context"": ""http://www.w3.org/ns/hydra/context.jsonld"",
""@type"": ""Collection"",
""member"": []
}";

var content = new StringContent(hydraImageBody, Encoding.UTF8, "application/json");
var path = $"/customers/{LegacyModeHelpers.LegacyCustomer}/queue";

// Act
var response = await httpClient.AsCustomer(LegacyModeHelpers.LegacyCustomer).PostAsync(path, content);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);

var model = await response.ReadAsHydraResponseAsync<DLCS.HydraModel.CustomerQueue>();
var dbBatch = dbContext.Batches.Single(a => a.Id == model.Id.GetLastPathElementAsInt());
dbBatch.Count.Should().Be(0);
}

[Fact]
public async Task Post_CreateBatch_201_IfLegacyModeEnabledWithAtIdFieldSet()
{
Expand Down Expand Up @@ -698,6 +722,26 @@ public async Task Post_CreateBatch_400_WithInvalidIdsSet()
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}

[Fact]
public async Task Post_CreateBatch_400_MembersEmpty()
{
// Arrange
var hydraImageBody = @"{
""@context"": ""http://www.w3.org/ns/hydra/context.jsonld"",
""@type"": ""Collection"",
""member"": []
}";

var content = new StringContent(hydraImageBody, Encoding.UTF8, "application/json");
const string path = "/customers/99/queue";

// Act
var response = await httpClient.AsCustomer(99).PostAsync(path, content);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}

[Fact]
public async Task Post_CreateBatch_400_IfSpaceNotFound()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,27 @@ public async Task Delete_PDF_Returns400_IfUnableToFind()
}

[Fact]
public async Task Delete_PDF_Returns400_IfArgsIncorrect()
public async Task Delete_PDF_Returns200_IfLessArgsThanQuery()
{
// Arrange
var path = "/customers/99/resources/pdf/cust-resource?args=too-little";

// Act
var response = await httpClient.AsCustomer().DeleteAsync(path);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}

[Fact]
public async Task Delete_PDF_Returns400_IfNoArgs()
{
// Arrange
var path = "/customers/99/resources/pdf/cust-resource";

// Act
var response = await httpClient.AsCustomer().DeleteAsync(path);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
Expand Down
8 changes: 7 additions & 1 deletion src/protagonist/API/Converters/LegacyModeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using DLCS.HydraModel;
using DLCS.Model.Assets;
using Hydra;
using Microsoft.Extensions.Logging;
using AssetFamily = DLCS.HydraModel.AssetFamily;

namespace API.Converters;
Expand All @@ -14,13 +15,16 @@ namespace API.Converters;
public static class LegacyModeConverter
{
private const string DefaultMediaType = "image/unknown";

internal static void LogLegacyUsage(this ILogger logger, string message, params object?[] args)
=> logger.LogWarning("LEGACY USE:" + message, args);

/// <summary>
/// Converts from legacy format to new format
/// </summary>
/// <param name="image">The image to convert should be emulated and translated into delivery channels</param>
/// <returns>A converted image</returns>
public static T VerifyAndConvertToModernFormat<T>(T image)
public static T VerifyAndConvertToModernFormat<T>(T image, ILogger? logger = null)
where T : Image
{
if (image.Origin.IsNullOrEmpty())
Expand All @@ -30,6 +34,7 @@ public static T VerifyAndConvertToModernFormat<T>(T image)

if (image.MediaType.IsNullOrEmpty())
{
logger?.LogLegacyUsage("Null or empty media type");
var contentType = image.Origin?.Split('.').Last() ?? string.Empty;

image.MediaType = MIMEHelper.GetContentTypeForExtension(contentType) ?? DefaultMediaType;
Expand All @@ -43,6 +48,7 @@ public static T VerifyAndConvertToModernFormat<T>(T image)

if (image.MaxUnauthorised is null or 0 && image.Roles.IsNullOrEmpty())
{
logger?.LogLegacyUsage("MaxUnauthorised");
image.MaxUnauthorised = -1;
}

Expand Down
21 changes: 11 additions & 10 deletions src/protagonist/API/Features/Image/Ingest/AssetProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@ public AssetProcessor(
/// </param>
/// <param name="requiresReingestPreSave">Optional delegate for modifying asset prior to saving</param>
/// <param name="cancellationToken">Current cancellation token</param>
/// <param name="isPriorityQueue">Whether the request is for the priority queue or not</param>
public async Task<ProcessAssetResult> Process(AssetBeforeProcessing assetBeforeProcessing, bool mustExist, bool alwaysReingest, bool isBatchUpdate,
Func<Asset, Task>? requiresReingestPreSave = null,
CancellationToken cancellationToken = default)
{
Asset? existingAsset;
try
{
existingAsset = await assetRepository.GetAsset(assetBeforeProcessing.Asset.Id, true);
var assetFromDatabase = await assetRepository.GetAsset(assetBeforeProcessing.Asset.Id, true, true);

if (existingAsset == null)
if (assetFromDatabase == null)
{
if (mustExist)
{
Expand Down Expand Up @@ -102,9 +102,10 @@ public async Task<ProcessAssetResult> Process(AssetBeforeProcessing assetBeforeP
)
};
}


var existingAsset = assetFromDatabase?.Clone();
var assetPreparationResult =
AssetPreparer.PrepareAssetForUpsert(existingAsset, assetBeforeProcessing.Asset, false, isBatchUpdate,
AssetPreparer.PrepareAssetForUpsert(assetFromDatabase, assetBeforeProcessing.Asset, false, isBatchUpdate,
settings.RestrictedAssetIdCharacters);

if (!assetPreparationResult.Success)
Expand All @@ -115,11 +116,11 @@ public async Task<ProcessAssetResult> Process(AssetBeforeProcessing assetBeforeP
WriteResult.FailedValidation)
};
}

var updatedAsset = assetPreparationResult.UpdatedAsset!; // this is from Database
var requiresEngineNotification = assetPreparationResult.RequiresReingest || alwaysReingest;

var deliveryChannelChanged = await deliveryChannelProcessor.ProcessImageDeliveryChannels(existingAsset,
var deliveryChannelChanged = await deliveryChannelProcessor.ProcessImageDeliveryChannels(assetFromDatabase,
updatedAsset, assetBeforeProcessing.DeliveryChannelsBeforeProcessing);
if (deliveryChannelChanged)
{
Expand All @@ -140,14 +141,14 @@ public async Task<ProcessAssetResult> Process(AssetBeforeProcessing assetBeforeP
updatedAsset.MarkAsFinished();
}

var assetAfterSave = await assetRepository.Save(updatedAsset, existingAsset != null, cancellationToken);
var assetAfterSave = await assetRepository.Save(updatedAsset, assetFromDatabase != null, cancellationToken);

return new ProcessAssetResult
{
ExistingAsset = existingAsset,
RequiresEngineNotification = requiresEngineNotification,
Result = ModifyEntityResult<Asset>.Success(assetAfterSave,
existingAsset == null ? WriteResult.Created : WriteResult.Updated)
assetFromDatabase == null ? WriteResult.Created : WriteResult.Updated)
};
}
catch (APIException apiEx)
Expand Down Expand Up @@ -175,4 +176,4 @@ public class ProcessAssetResult
public bool RequiresEngineNotification { get; set; }

public bool IsSuccess => Result.IsSuccess;
}
}
Loading
Loading