From fa25a63ccb35e4dce0fa75140e22641504d68a78 Mon Sep 17 00:00:00 2001 From: tomcrane Date: Thu, 21 Dec 2023 17:53:33 +0000 Subject: [PATCH] get recursive archival group --- .../Fedora/ApiModel/BinaryMetadataResponse.cs | 3 +- LeedsExperiment/Fedora/ArchivalGroup.cs | 5 + LeedsExperiment/Fedora/Binary.cs | 5 + LeedsExperiment/Fedora/Container.cs | 7 + LeedsExperiment/Fedora/IFedora.cs | 3 + LeedsExperiment/Fedora/IStorageMapper.cs | 8 + LeedsExperiment/Fedora/Storage/StorageMap.cs | 24 +++ LeedsExperiment/Fedora/Storage/Version.cs | 8 + LeedsExperiment/Preservation/FedoraWrapper.cs | 183 +++++++++++++++--- LeedsExperiment/Preservation/JsonLdX.cs | 18 ++ .../Preservation/S3StorageMapper.cs | 13 ++ .../Properties/launchSettings.json | 2 +- LeedsExperiment/SamplesWorker/Worker.cs | 53 +++++ 13 files changed, 306 insertions(+), 26 deletions(-) create mode 100644 LeedsExperiment/Fedora/IStorageMapper.cs create mode 100644 LeedsExperiment/Fedora/Storage/StorageMap.cs create mode 100644 LeedsExperiment/Fedora/Storage/Version.cs create mode 100644 LeedsExperiment/Preservation/JsonLdX.cs create mode 100644 LeedsExperiment/Preservation/S3StorageMapper.cs diff --git a/LeedsExperiment/Fedora/ApiModel/BinaryMetadataResponse.cs b/LeedsExperiment/Fedora/ApiModel/BinaryMetadataResponse.cs index 966124d..6f69718 100644 --- a/LeedsExperiment/Fedora/ApiModel/BinaryMetadataResponse.cs +++ b/LeedsExperiment/Fedora/ApiModel/BinaryMetadataResponse.cs @@ -12,9 +12,10 @@ public class BinaryMetadataResponse : FedoraJsonLdResponse [JsonPropertyOrder(202)] public string? ContentType { get; set; } + // This comes back as a string [JsonPropertyName("hasSize")] [JsonPropertyOrder(203)] - public long Size { get; set; } + public string Size { get; set; } [JsonPropertyName("hasMessageDigest")] [JsonPropertyOrder(211)] diff --git a/LeedsExperiment/Fedora/ArchivalGroup.cs b/LeedsExperiment/Fedora/ArchivalGroup.cs index 592eba4..65d381e 100644 --- a/LeedsExperiment/Fedora/ArchivalGroup.cs +++ b/LeedsExperiment/Fedora/ArchivalGroup.cs @@ -1,4 +1,5 @@ using Fedora.ApiModel; +using Fedora.Storage; namespace Fedora; @@ -9,6 +10,10 @@ public ArchivalGroup(FedoraJsonLdResponse fedoraResponse) : base(fedoraResponse) } + public StorageMap? StorageMap { get; set; } + + public Storage.Version[] Versions { get; set; } + public Uri GetResourceUri(string path) { // the Location property won't end with a trailing slash, so we can't create URIs with the normal Uri constructor diff --git a/LeedsExperiment/Fedora/Binary.cs b/LeedsExperiment/Fedora/Binary.cs index 7162401..8e20b89 100644 --- a/LeedsExperiment/Fedora/Binary.cs +++ b/LeedsExperiment/Fedora/Binary.cs @@ -20,5 +20,10 @@ public string Origin public string? ContentType { get; set; } public long Size { get; set; } public string? Digest { get; set; } + + public override string ToString() + { + return $"🗎 {Name ?? GetType().Name}"; + } } } diff --git a/LeedsExperiment/Fedora/Container.cs b/LeedsExperiment/Fedora/Container.cs index 46eca98..f1e53a0 100644 --- a/LeedsExperiment/Fedora/Container.cs +++ b/LeedsExperiment/Fedora/Container.cs @@ -8,7 +8,14 @@ public Container(FedoraJsonLdResponse jsonLdResponse) : base(jsonLdResponse) { } + public bool Populated { get; set; } = false; + public required List Containers { get; set; } = new List(); public required List Binaries { get; set; } = new List(); + + public override string ToString() + { + return $"📁 {Name ?? GetType().Name}"; + } } } diff --git a/LeedsExperiment/Fedora/IFedora.cs b/LeedsExperiment/Fedora/IFedora.cs index c13b44d..bcd844c 100644 --- a/LeedsExperiment/Fedora/IFedora.cs +++ b/LeedsExperiment/Fedora/IFedora.cs @@ -9,6 +9,9 @@ public interface IFedora Task GetObject(Uri uri, Transaction? transaction = null) where T: Resource; Task GetObject(string path, Transaction? transaction = null) where T : Resource; + Task GetPopulatedArchivalGroup(Uri uri, string? version = null, Transaction? transaction = null); + Task GetPopulatedArchivalGroup(string path, string? version = null, Transaction? transaction = null); + Task CreateArchivalGroup(Uri parent, string slug, string name, Transaction? transaction = null); Task CreateArchivalGroup(string parentPath, string slug, string name, Transaction? transaction = null); Task CreateContainer(Uri parent, string slug, string name, Transaction? transaction = null); diff --git a/LeedsExperiment/Fedora/IStorageMapper.cs b/LeedsExperiment/Fedora/IStorageMapper.cs new file mode 100644 index 0000000..bb11e65 --- /dev/null +++ b/LeedsExperiment/Fedora/IStorageMapper.cs @@ -0,0 +1,8 @@ +using Fedora.Storage; + +namespace Fedora; + +public interface IStorageMapper +{ + Task GetStorageMap(ArchivalGroup archivalGroup, string? version = null); +} diff --git a/LeedsExperiment/Fedora/Storage/StorageMap.cs b/LeedsExperiment/Fedora/Storage/StorageMap.cs new file mode 100644 index 0000000..6cdd377 --- /dev/null +++ b/LeedsExperiment/Fedora/Storage/StorageMap.cs @@ -0,0 +1,24 @@ +namespace Fedora.Storage; + +public class StorageMap +{ + // v1, v2 + public string Version { get; set; } + + // e.g., S3 + public string StorageType { get; set; } + + // e.g., a bucket + public string Root { get; set; } + + // The key of the object container + public string ObjectPath { get; set; } + + public Dictionary Files { get; set; } +} + +public class OriginFile +{ + public string Hash { get; set; } + public string FullPath { get; set; } +} diff --git a/LeedsExperiment/Fedora/Storage/Version.cs b/LeedsExperiment/Fedora/Storage/Version.cs new file mode 100644 index 0000000..2ef548a --- /dev/null +++ b/LeedsExperiment/Fedora/Storage/Version.cs @@ -0,0 +1,8 @@ +namespace Fedora.Storage; + +public class Version +{ + public string MementoTimestamp { get; set; } + public DateTime LastModified { get; set; } + public string OcflVersion { get; set; } +} diff --git a/LeedsExperiment/Preservation/FedoraWrapper.cs b/LeedsExperiment/Preservation/FedoraWrapper.cs index cfbb976..48b95f7 100644 --- a/LeedsExperiment/Preservation/FedoraWrapper.cs +++ b/LeedsExperiment/Preservation/FedoraWrapper.cs @@ -1,19 +1,23 @@ using Fedora; using Fedora.ApiModel; using Fedora.Vocab; +using System.Linq; using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Text.Json; namespace Preservation; public class FedoraWrapper : IFedora { - private readonly HttpClient _httpClient; + private readonly HttpClient httpClient; + private readonly IStorageMapper storageMapper; - public FedoraWrapper(HttpClient httpClient) + public FedoraWrapper(HttpClient httpClient, IStorageMapper storageMapper) { - _httpClient = httpClient; + this.httpClient = httpClient; + this.storageMapper = storageMapper; } public async Task Proxy(string contentType, string path, string? jsonLdMode = null, bool preferContained = false) @@ -38,7 +42,7 @@ public async Task Proxy(string contentType, string path, string? jsonLdM req.WithContainedDescriptions(); } req.RequestUri = new Uri(path, UriKind.Relative); - var response = await _httpClient.SendAsync(req); + var response = await httpClient.SendAsync(req); var raw = await response.Content.ReadAsStringAsync(); return raw; } @@ -51,7 +55,7 @@ public async Task Proxy(string contentType, string path, string? jsonLdM public async Task CreateArchivalGroup(string parentPath, string slug, string name, Transaction? transaction = null) { - var parent = new Uri(_httpClient.BaseAddress!, parentPath); + var parent = new Uri(httpClient.BaseAddress!, parentPath); return await CreateContainerInternal(true, parent, slug, name, transaction) as ArchivalGroup; } public async Task CreateContainer(Uri parent, string slug, string name, Transaction? transaction = null) @@ -61,7 +65,7 @@ public async Task Proxy(string contentType, string path, string? jsonLdM public async Task CreateContainer(string parentPath, string slug, string name, Transaction? transaction = null) { - var parent = new Uri(_httpClient.BaseAddress!, parentPath); + var parent = new Uri(httpClient.BaseAddress!, parentPath); return await CreateContainerInternal(false, parent, slug, name, transaction); } @@ -75,14 +79,14 @@ public async Task Proxy(string contentType, string path, string? jsonLdM { req.AsArchivalGroup(); } - var response = await _httpClient.SendAsync(req); + var response = await httpClient.SendAsync(req); response.EnsureSuccessStatusCode(); // The body is the new resource URL var newReq = MakeHttpRequestMessage(response.Headers.Location!, HttpMethod.Get) .InTransaction(transaction) .ForJsonLd(); - var newResponse = await _httpClient.SendAsync(newReq); + var newResponse = await httpClient.SendAsync(newReq); var containerResponse = await MakeFedoraResponse(newResponse); if (containerResponse != null) @@ -130,7 +134,7 @@ private async Task PutOrPostBinary(HttpMethod httpMethod, Uri location, throw new InvalidOperationException("Initial checksum doesn't match"); } var req = MakeBinaryPutOrPost(httpMethod, location, localFile, originalName, contentType, transaction, expected); - var response = await _httpClient.SendAsync(req); + var response = await httpClient.SendAsync(req); if (httpMethod == HttpMethod.Put && response.StatusCode == HttpStatusCode.Gone) { // https://github.com/fcrepo/fcrepo/pull/2044 @@ -141,7 +145,7 @@ private async Task PutOrPostBinary(HttpMethod httpMethod, Uri location, // Log or record somehow that this has happened? var retryReq = MakeBinaryPutOrPost(httpMethod, location, localFile, originalName, contentType, transaction, expected) .OverwriteTombstone(); - response = await _httpClient.SendAsync(retryReq); + response = await httpClient.SendAsync(retryReq); } response.EnsureSuccessStatusCode(); @@ -149,7 +153,7 @@ private async Task PutOrPostBinary(HttpMethod httpMethod, Uri location, var newReq = MakeHttpRequestMessage(resourceLocation.MetadataUri(), HttpMethod.Get) .InTransaction(transaction) .ForJsonLd(); - var newResponse = await _httpClient.SendAsync(newReq); + var newResponse = await httpClient.SendAsync(newReq); var binaryResponse = await MakeFedoraResponse(newResponse); if (binaryResponse.Title == null) @@ -158,20 +162,20 @@ private async Task PutOrPostBinary(HttpMethod httpMethod, Uri location, var patchReq = MakeHttpRequestMessage(resourceLocation.MetadataUri(), HttpMethod.Patch) .InTransaction(transaction); patchReq.AsInsertTitlePatch(originalName); - var patchResponse = await _httpClient.SendAsync(patchReq); + var patchResponse = await httpClient.SendAsync(patchReq); patchResponse.EnsureSuccessStatusCode(); // now ask again: var retryMetadataReq = MakeHttpRequestMessage(resourceLocation.MetadataUri(), HttpMethod.Get) .InTransaction(transaction) .ForJsonLd(); - var afterPatchResponse = await _httpClient.SendAsync(retryMetadataReq); + var afterPatchResponse = await httpClient.SendAsync(retryMetadataReq); binaryResponse = await MakeFedoraResponse(afterPatchResponse); } var binary = new Binary(binaryResponse) { Location = binaryResponse.Id, FileName = binaryResponse.FileName, - Size = binaryResponse.Size, + Size = Convert.ToInt64(binaryResponse.Size), Digest = binaryResponse.Digest?.Split(':')[^1], ContentType = binaryResponse.ContentType }; @@ -209,7 +213,7 @@ private HttpRequestMessage MakeBinaryPutOrPost(HttpMethod httpMethod, Uri locati public async Task BeginTransaction() { var req = MakeHttpRequestMessage("./fcr:tx", HttpMethod.Post); // note URI construction because of the colon - var response = await _httpClient.SendAsync(req); + var response = await httpClient.SendAsync(req); response.EnsureSuccessStatusCode(); var tx = new Transaction { @@ -231,7 +235,7 @@ public async Task BeginTransaction() public async Task CheckTransaction(Transaction tx) { HttpRequestMessage req = MakeHttpRequestMessage(tx.Location, HttpMethod.Get); - var response = await _httpClient.SendAsync(req); + var response = await httpClient.SendAsync(req); switch (response.StatusCode) { case HttpStatusCode.NoContent: @@ -252,7 +256,7 @@ public async Task CheckTransaction(Transaction tx) public async Task KeepTransactionAlive(Transaction tx) { HttpRequestMessage req = MakeHttpRequestMessage(tx.Location, HttpMethod.Post); - var response = await _httpClient.SendAsync(req); + var response = await httpClient.SendAsync(req); response.EnsureSuccessStatusCode(); if (response.Headers.TryGetValues("Atomic-Expires", out IEnumerable? values)) @@ -264,7 +268,7 @@ public async Task KeepTransactionAlive(Transaction tx) public async Task CommitTransaction(Transaction tx) { HttpRequestMessage req = MakeHttpRequestMessage(tx.Location, HttpMethod.Put); - var response = await _httpClient.SendAsync(req); + var response = await httpClient.SendAsync(req); switch (response.StatusCode) { case HttpStatusCode.NoContent: @@ -289,7 +293,7 @@ public async Task CommitTransaction(Transaction tx) public async Task RollbackTransaction(Transaction tx) { HttpRequestMessage req = MakeHttpRequestMessage(tx.Location, HttpMethod.Delete); - var response = await _httpClient.SendAsync(req); + var response = await httpClient.SendAsync(req); switch (response.StatusCode) { case HttpStatusCode.NoContent: @@ -323,6 +327,7 @@ private HttpRequestMessage MakeHttpRequestMessage(Uri uri, HttpMethod method) private static async Task MakeFedoraResponse(HttpResponseMessage response) where T : FedoraJsonLdResponse { + // works for SINGLE resources, not contained responses that send back a @graph var fedoraResponse = await response.Content.ReadFromJsonAsync(); if (fedoraResponse != null) { @@ -334,7 +339,7 @@ private HttpRequestMessage MakeHttpRequestMessage(Uri uri, HttpMethod method) } public async Task GetObject(string path, Transaction? transaction = null) where T : Resource { - var uri = new Uri(_httpClient.BaseAddress!, path); + var uri = new Uri(httpClient.BaseAddress!, path); return await GetObject(uri, transaction); } @@ -344,7 +349,7 @@ private HttpRequestMessage MakeHttpRequestMessage(Uri uri, HttpMethod method) var reqUri = isBinary ? uri.MetadataUri() : uri; var request = MakeHttpRequestMessage(reqUri, HttpMethod.Get) .ForJsonLd(); - var response = await _httpClient.SendAsync(request); + var response = await httpClient.SendAsync(request); if (isBinary) { @@ -353,7 +358,7 @@ private HttpRequestMessage MakeHttpRequestMessage(Uri uri, HttpMethod method) { Location = fileResponse.Id, FileName = fileResponse.FileName, - Size = fileResponse.Size, + Size = Convert.ToInt64(fileResponse.Size), Digest = fileResponse.Digest, ContentType = fileResponse.ContentType }; @@ -389,9 +394,139 @@ private HttpRequestMessage MakeHttpRequestMessage(Uri uri, HttpMethod method) return null; } + public async Task GetPopulatedArchivalGroup(string path, string? version = null, Transaction? transaction = null) + { + var uri = new Uri(httpClient.BaseAddress!, path); + return await GetPopulatedArchivalGroup(uri, version, transaction); + } + + + public async Task GetPopulatedArchivalGroup(Uri uri, string? version = null, Transaction? transaction = null) + { + if(version != null) + { + 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 + } + var archivalGroup = await GetPopulatedContainer(uri, true, transaction) as ArchivalGroup; + if(archivalGroup == null) + { + return null; + } + + // Even if you don't ask for a specific version, we still list all the available versions. + // archivalGroup.Versions = /// what interface supplies this? - get it from Fedora first then pass to storage mapper to fill in the ocfl stuff? + + // Our version class mixes Memento and OCFL versions, we need to correlate them + + archivalGroup.StorageMap = await storageMapper.GetStorageMap(archivalGroup); + return archivalGroup; + } + + public async Task GetPopulatedContainer(Uri uri, bool isArchivalGroup, Transaction? transaction = 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 + + // what's most efficient way? + var response = await httpClient.SendAsync(request); + if(isArchivalGroup && !response.HasArchivalGroupHeader()) + { + throw new InvalidOperationException("Response is not an Archival Group"); + } + + var content = await response.Content.ReadAsStringAsync(); + + using(JsonDocument jDoc = JsonDocument.Parse(content)) + { + JsonElement[]? containerAndContained = null; + if (jDoc.RootElement.TryGetProperty("@graph", out JsonElement graph)) + { + containerAndContained = [.. graph.EnumerateArray()]; + } + else + { + if (jDoc.RootElement.TryGetProperty("@id", out JsonElement idElement)) + { + containerAndContained = [jDoc.RootElement]; + } + } + + if (containerAndContained == null || containerAndContained.Length == 0) + { + throw new InvalidOperationException("Could not parse Archival Group"); + } + if (containerAndContained[0].GetProperty("@id").GetString() != uri.ToString()) + { + throw new InvalidOperationException("First resource in graph should be the asked-for URI"); + } + + // Make a map of the IDs + Dictionary dict = containerAndContained.ToDictionary(x => x.GetProperty("@id").GetString()!); + var fedoraObject = JsonSerializer.Deserialize(containerAndContained[0]); + Container topContainer; + if(isArchivalGroup) + { + topContainer = new ArchivalGroup(fedoraObject) + { + Location = fedoraObject.Id, + Binaries = [], + Containers = [] + }; + } + else + { + topContainer = new Container(fedoraObject) + { + Location = fedoraObject.Id, + Binaries = [], + Containers = [] + }; + + } + // Get the contains property which may be a single value or an array + List childIds = []; + if (containerAndContained[0].TryGetProperty("contains", out JsonElement contains)) + { + if(contains.ValueKind == JsonValueKind.String) + { + childIds = [contains.GetString()!]; + } + else if(contains.ValueKind == JsonValueKind.Array) + { + childIds = contains.EnumerateArray().Select(x => x.GetString()!).ToList(); + } + foreach (var id in childIds) + { + var resource = dict[id]; + if (resource.HasType("fedora:Container")) + { + var fedoraContainer = JsonSerializer.Deserialize(resource); + var container = await GetPopulatedContainer(fedoraContainer.Id, false, transaction); + topContainer.Containers.Add(container); + } + else if(resource.HasType("fedora:Binary")) + { + var fedoraBinary = JsonSerializer.Deserialize(resource); + var binary = new Binary(fedoraBinary) + { + Location = fedoraBinary.Id, + }; + topContainer.Binaries.Add(binary); + } + } + } + return topContainer; + } + } + + public string? GetOrigin(ArchivalGroup versionedParent, Resource? childResource = null) { - string basePath = _httpClient.BaseAddress.AbsolutePath; + string basePath = httpClient.BaseAddress.AbsolutePath; string absPath = versionedParent.Location?.AbsolutePath ?? string.Empty; if (!absPath.StartsWith(basePath)) { @@ -409,7 +544,7 @@ public async Task Delete(Uri uri, Transaction? transaction = null) HttpRequestMessage req = MakeHttpRequestMessage(uri, HttpMethod.Delete) .InTransaction(transaction); - var response = await _httpClient.SendAsync(req); + var response = await httpClient.SendAsync(req); response.EnsureSuccessStatusCode(); } diff --git a/LeedsExperiment/Preservation/JsonLdX.cs b/LeedsExperiment/Preservation/JsonLdX.cs new file mode 100644 index 0000000..fcab066 --- /dev/null +++ b/LeedsExperiment/Preservation/JsonLdX.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Preservation; + +public static class JsonLdX +{ + public static bool HasType(this JsonElement element, string type) + { + if (element.TryGetProperty("@type", out JsonElement typeList)) + { + if (typeList.EnumerateArray().Any(t => t.GetString() == type)) + { + return true; + } + } + return false; + } +} diff --git a/LeedsExperiment/Preservation/S3StorageMapper.cs b/LeedsExperiment/Preservation/S3StorageMapper.cs new file mode 100644 index 0000000..8ec2852 --- /dev/null +++ b/LeedsExperiment/Preservation/S3StorageMapper.cs @@ -0,0 +1,13 @@ +using Fedora; +using Fedora.Storage; + +namespace Preservation +{ + public class S3StorageMapper : IStorageMapper + { + public Task GetStorageMap(ArchivalGroup archivalGroup, string? version = null) + { + throw new NotImplementedException(); + } + } +} diff --git a/LeedsExperiment/SamplesWorker/Properties/launchSettings.json b/LeedsExperiment/SamplesWorker/Properties/launchSettings.json index db3aa43..c36a932 100644 --- a/LeedsExperiment/SamplesWorker/Properties/launchSettings.json +++ b/LeedsExperiment/SamplesWorker/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "SamplesWorker": { "commandName": "Project", - "commandLineArgs": "ocfl", + "commandLineArgs": "ag", "environmentVariables": { "DOTNET_ENVIRONMENT": "Development" }, diff --git a/LeedsExperiment/SamplesWorker/Worker.cs b/LeedsExperiment/SamplesWorker/Worker.cs index e4fca2c..0bc3402 100644 --- a/LeedsExperiment/SamplesWorker/Worker.cs +++ b/LeedsExperiment/SamplesWorker/Worker.cs @@ -28,7 +28,14 @@ private async Task EntryPoint(string[] args, CancellationToken stoppingToken) var ag2 = await OcflV2(ag); // Can't run 3 until tombstone issue resolved // var ag3 = await OcflV3(ag2); + var altAg3 = await OcflV3Alt(ag2); + var altAg4 = await OcflV4Alt(altAg3); break; + + case "ag": + await GetAg("storage-01/ocfl-expt-12-21-23-9-58-38"); + break; + default: await DoDefault(); break; @@ -36,6 +43,10 @@ private async Task EntryPoint(string[] args, CancellationToken stoppingToken) } } + private async Task GetAg(string path) + { + var ag = await fedora.GetPopulatedArchivalGroup(path); + } private async Task DoDefault() { @@ -132,6 +143,48 @@ private async Task OcflV3(ArchivalGroup archivalGroup) return reObtainedAG; } + + private async Task OcflV3Alt(ArchivalGroup archivalGroup) + { + var localPath = @"C:\Users\TomCrane\Dropbox\digirati\leeds\fedora-experiments\versioned-example\working\v3Alt"; + + var transaction = await fedora.BeginTransaction(); + Console.WriteLine("In transaction for v3Alt {0}", transaction.Location); + + // Add another image + var localJpg = new FileInfo(Path.Combine(localPath, "picture.jpg")); + var putLocation = archivalGroup.GetResourceUri("picture.jpg"); + var fedoraJpg = await fedora.PutBinary(putLocation, localJpg, localJpg.Name, "image/jpeg", transaction); + + // end transaction + await fedora.CommitTransaction(transaction); + + var reObtainedAG = await fedora.GetObject(archivalGroup.Location); + Console.WriteLine("archivalGroup v3Alt at {0}", fedora.GetOrigin(reObtainedAG)); + return reObtainedAG; + } + + private async Task OcflV4Alt(ArchivalGroup archivalGroup) + { + var localPath = @"C:\Users\TomCrane\Dropbox\digirati\leeds\fedora-experiments\versioned-example\working\v4Alt"; + + var transaction = await fedora.BeginTransaction(); + Console.WriteLine("In transaction for v4Alt {0}", transaction.Location); + + // CHANGE the image at the picture.jpg URI + var localJpg = new FileInfo(Path.Combine(localPath, "picture.jpg")); + var putLocation = archivalGroup.GetResourceUri("picture.jpg"); + var fedoraJpg = await fedora.PutBinary(putLocation, localJpg, localJpg.Name, "image/jpeg", transaction); + + // end transaction + await fedora.CommitTransaction(transaction); + + var reObtainedAG = await fedora.GetObject(archivalGroup.Location); + Console.WriteLine("archivalGroup v4Alt at {0}", fedora.GetOrigin(reObtainedAG)); + return reObtainedAG; + } + + /* * Demonstrate that we can * - enumerate the files mentioned above