Skip to content

Commit

Permalink
date time expts
Browse files Browse the repository at this point in the history
  • Loading branch information
tomcrane committed Jan 4, 2024
1 parent 80ae6a2 commit 0e3ed72
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 32 deletions.
2 changes: 1 addition & 1 deletion LeedsExperiment/Fedora/IFedora.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Fedora
{
public interface IFedora
{
Task<string> Proxy(string contentType, string path, string? jsonLdMode = null, bool preferContained = false);
Task<string> Proxy(string contentType, string path, string? jsonLdMode = null, bool preferContained = false, string? acceptDate = null);

Task<T?> GetObject<T>(Uri uri, Transaction? transaction = null) where T: Resource;
Task<T?> GetObject<T>(string path, Transaction? transaction = null) where T : Resource;
Expand Down
29 changes: 29 additions & 0 deletions LeedsExperiment/Fedora/Vocab/MementoDateTime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

using System.Globalization;

namespace Fedora.Vocab;

public static class MementoDateTime
{
public const string Format = "yyyyMMddHHmmss";

public static DateTime DateTimeFromMementoTimestamp(this string mementoTimestamp)
{
return DateTime.ParseExact(mementoTimestamp, Format, CultureInfo.InvariantCulture, DateTimeStyles.None);
}

public static string ToMementoTimestamp(this DateTime dt)
{
return dt.ToString(Format);
}

public static string ToRFC1123(this DateTime dt)
{
return dt.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern);
}

public static string ToRFC1123(this string mementoTimestamp)
{
return DateTimeFromMementoTimestamp(mementoTimestamp).ToRFC1123();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ public async Task<IActionResult> Index(string contentTypeMajor, string contentTy
if (jsonld == "flattened") { jsonLdMode = JsonLdModes.Flattened; }

bool contained = Convert.ToBoolean(Request.Query["contained"]);
string? acceptDate = Request.Query["acceptDate"];

// in WebAPI, path is not giving us the full path
var fullPath = string.Join("/", Request.Path.ToString().Split('/')[5..]);
var contentType = $"{contentTypeMajor}/{contentTypeMinor}";
var result = await fedora.Proxy(contentType, fullPath, jsonLdMode, contained);
var result = await fedora.Proxy(contentType, fullPath, jsonLdMode, contained, acceptDate);
return Content(result, contentType);
}

Expand Down
60 changes: 31 additions & 29 deletions LeedsExperiment/Preservation/FedoraWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Fedora.ApiModel;
using Fedora.Storage;
using Fedora.Vocab;
using System.Globalization;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
Expand All @@ -21,7 +20,7 @@ public FedoraWrapper(HttpClient httpClient, IStorageMapper storageMapper)
this.storageMapper = storageMapper;
}

public async Task<string> Proxy(string contentType, string path, string? jsonLdMode = null, bool preferContained = false)
public async Task<string> Proxy(string contentType, string path, string? jsonLdMode = null, bool preferContained = false, string? acceptDate = null)
{
var req = new HttpRequestMessage();
req.Headers.Accept.Clear();
Expand All @@ -42,6 +41,7 @@ public async Task<string> Proxy(string contentType, string path, string? jsonLdM
{
req.WithContainedDescriptions();
}
req.WithAcceptDate(acceptDate);
req.RequestUri = new Uri(path, UriKind.Relative);
var response = await httpClient.SendAsync(req);
var raw = await response.Content.ReadAsStringAsync();
Expand Down Expand Up @@ -404,49 +404,49 @@ private HttpRequestMessage MakeHttpRequestMessage(Uri uri, HttpMethod method)

public async Task<ArchivalGroup?> GetPopulatedArchivalGroup(Uri uri, string? version = null, Transaction? transaction = null)
{
if(version != null)
var versions = await GetFedoraVersions(uri);
var storageMap = await storageMapper.GetStorageMap(uri, version);
MergeVersions(versions, storageMap.AllVersions);
ObjectVersion? objectVersion = null;
if(!string.IsNullOrWhiteSpace(version))
{
// throw new NotImplementedException("Can't ask for fedora version yet");
// we'll need to carry version all the way through this, GetPopulatedContainer will need to take version
objectVersion = versions.Single(v => v.MementoTimestamp == version || v.OcflVersion == version);
}
var archivalGroup = await GetPopulatedContainer(uri, true, transaction) as ArchivalGroup;

var archivalGroup = await GetPopulatedContainer(uri, true, transaction, objectVersion) as ArchivalGroup;
if(archivalGroup == null)
{
return null;
}
if(archivalGroup.Location != uri)
{
throw new InvalidOperationException("location doesnt match uri");
}

archivalGroup.Origin = storageMapper.GetArchivalGroupOrigin(archivalGroup.Location);

// Partially populate
archivalGroup.Versions = await GetFedoraVersions(uri);

archivalGroup.StorageMap = await storageMapper.GetStorageMap(archivalGroup.Location, version);

MergeVersions(archivalGroup);
archivalGroup.Versions = versions;
archivalGroup.StorageMap = storageMap;

return archivalGroup;
}

private void MergeVersions(ArchivalGroup archivalGroup)
private void MergeVersions(ObjectVersion[] fedoraVersions, ObjectVersion[] ocflVersions)
{
if(archivalGroup.Versions.Length != archivalGroup.StorageMap.AllVersions.Length)
if(fedoraVersions.Length != ocflVersions.Length)
{
throw new InvalidOperationException("Fedora reports a different number of versions from OCFL");
}
for(int i = 0; i < archivalGroup.Versions.Length; i++)
for(int i = 0; i < fedoraVersions.Length; i++)
{
var fedoraVersion = archivalGroup.Versions[i];
var ocflVersion = archivalGroup.StorageMap.AllVersions[i];
if(fedoraVersion.MementoTimestamp != ocflVersion.MementoTimestamp)
if(fedoraVersions[i].MementoTimestamp != ocflVersions[i].MementoTimestamp)
{
throw new InvalidOperationException($"Fedora reports a different MementoTimestamp {fedoraVersion.MementoTimestamp} from OCFL: {ocflVersion.MementoTimestamp}");
throw new InvalidOperationException($"Fedora reports a different MementoTimestamp {fedoraVersions[i].MementoTimestamp} from OCFL: {ocflVersions[i].MementoTimestamp}");
}
fedoraVersion.OcflVersion = ocflVersion.OcflVersion;
fedoraVersions[i].OcflVersion = ocflVersions[i].OcflVersion;
}
archivalGroup.Version = archivalGroup.StorageMap.Version;
}

private async Task<ObjectVersion[]?> GetFedoraVersions(Uri uri)
private async Task<ObjectVersion[]> GetFedoraVersions(Uri uri)
{
var request = MakeHttpRequestMessage(uri.VersionsUri(), HttpMethod.Get)
.ForJsonLd();
Expand All @@ -463,17 +463,13 @@ private void MergeVersions(ArchivalGroup archivalGroup)
// We're not going to learn anything more than we would by parsing the memento path elements - which is TERRIBLY non-REST-y
return childIds
.Select(id => id.Split('/').Last())
.Select(p => new ObjectVersion { MementoTimestamp = p, MementoDateTime = GetDateFromMemento(p) })
.Select(p => new ObjectVersion { MementoTimestamp = p, MementoDateTime = p.DateTimeFromMementoTimestamp() })
.OrderBy(ov => ov.MementoTimestamp)
.ToArray();
}

}

private DateTime GetDateFromMemento(string mementoFormat)
{
return DateTime.ParseExact(mementoFormat, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None);
}

private List<string> GetIdsFromContainsProperty(JsonElement element)
{
Expand All @@ -492,14 +488,20 @@ private List<string> GetIdsFromContainsProperty(JsonElement element)
return childIds;
}

public async Task<Container?> GetPopulatedContainer(Uri uri, bool isArchivalGroup, Transaction? transaction = null)
public async Task<Container?> GetPopulatedContainer(Uri uri, bool isArchivalGroup, Transaction? transaction = null, ObjectVersion? objectVersion = null)
{
var request = MakeHttpRequestMessage(uri, HttpMethod.Get)
.ForJsonLd()
.WithContainedDescriptions();

// WithContainedDescriptions could return @graph or it could return a single object if the container has no children

// PROBLEM - I can't just append /fcr:version/20240103160421 because that causes an error if you also ask for .WithContainedDescriptions()
// "Invalid request for memento"
// presumably because the contained descriptions don't each have that version?

// but

// what's most efficient way?
var response = await httpClient.SendAsync(request);
bool hasArchivalGroupHeader = response.HasArchivalGroupHeader();
Expand Down
3 changes: 2 additions & 1 deletion LeedsExperiment/Preservation/OcflS3StorageMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Fedora;
using Fedora.Storage;
using Fedora.Storage.Ocfl;
using Fedora.Vocab;
using Microsoft.Extensions.Options;
using System.Text.Json;

Expand Down Expand Up @@ -36,7 +37,7 @@ public async Task<StorageMap> GetStorageMap(Uri archivalGroupUri, string? versio
{
OcflVersion = kvp.Key,
MementoDateTime = kvp.Value.Created,
MementoTimestamp = kvp.Value.Created.ToString("yyyyMMddHHmmss")
MementoTimestamp = kvp.Value.Created.ToMementoTimestamp(),
})
.OrderBy(o => o.MementoDateTime)
.ToList();
Expand Down
17 changes: 17 additions & 0 deletions LeedsExperiment/Preservation/RequestX.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Security.Cryptography;
using Fedora.ApiModel;
using Fedora.Vocab;

Expand Down Expand Up @@ -68,6 +69,22 @@ public static HttpRequestMessage WithDigest(this HttpRequestMessage requestMessa
return requestMessage;
}


public static HttpRequestMessage WithAcceptDate(this HttpRequestMessage requestMessage, string? mementoTimestamp)
{
if (!string.IsNullOrWhiteSpace(mementoTimestamp))
{
requestMessage.Headers.Add("Accept-Datetime", mementoTimestamp.ToRFC1123());
}
return requestMessage;
}

public static HttpRequestMessage WithAcceptDate(this HttpRequestMessage requestMessage, DateTime dt)
{
requestMessage.Headers.Add("Accept-Datetime", dt.ToRFC1123());
return requestMessage;
}

public static HttpRequestMessage WithSlug(this HttpRequestMessage requestMessage, string slug)
{
requestMessage.Headers.Add("slug", slug);
Expand Down

0 comments on commit 0e3ed72

Please sign in to comment.