diff --git a/LeedsExperiment/Fedora/ApiModel/FedoraJsonLdResponse.cs b/LeedsExperiment/Fedora/ApiModel/FedoraJsonLdResponse.cs
index 1a801b3..0283a66 100644
--- a/LeedsExperiment/Fedora/ApiModel/FedoraJsonLdResponse.cs
+++ b/LeedsExperiment/Fedora/ApiModel/FedoraJsonLdResponse.cs
@@ -10,7 +10,7 @@ public class FedoraJsonLdResponse
[JsonPropertyName("@id")]
[JsonPropertyOrder(1)]
- public string? Id { get; set; }
+ public string Id { get; set; }
[JsonPropertyName("@type")]
[JsonPropertyOrder(2)]
@@ -34,6 +34,9 @@ public class FedoraJsonLdResponse
// Our custom(ish) additions
+ ///
+ /// Assumes we are using dc:title and that Fedora maps it to title
+ ///
[JsonPropertyName("title")]
[JsonPropertyOrder(101)]
public string? Title { get; set; }
diff --git a/LeedsExperiment/Fedora/ApiModel/Transaction.cs b/LeedsExperiment/Fedora/ApiModel/Transaction.cs
new file mode 100644
index 0000000..c7f959f
--- /dev/null
+++ b/LeedsExperiment/Fedora/ApiModel/Transaction.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Fedora.ApiModel
+{
+ public class Transaction
+ {
+ public Uri Location { get; set; }
+ public DateTime Expires { get; set; }
+ public bool Expired { get; set; }
+ public bool Committed { get; set; }
+
+ public const string HeaderName = "Atomic-ID";
+ }
+}
diff --git a/LeedsExperiment/Fedora/ArchivalGroup.cs b/LeedsExperiment/Fedora/ArchivalGroup.cs
index 5188a54..7b3317f 100644
--- a/LeedsExperiment/Fedora/ArchivalGroup.cs
+++ b/LeedsExperiment/Fedora/ArchivalGroup.cs
@@ -1,4 +1,5 @@
-using System;
+using Fedora.ApiModel;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -8,5 +9,9 @@ namespace Fedora
{
public class ArchivalGroup : Directory
{
+ public ArchivalGroup(FedoraJsonLdResponse fedoraResponse) : base(fedoraResponse)
+ {
+
+ }
}
}
diff --git a/LeedsExperiment/Fedora/Directory.cs b/LeedsExperiment/Fedora/Directory.cs
index 3dc0e1a..8981e21 100644
--- a/LeedsExperiment/Fedora/Directory.cs
+++ b/LeedsExperiment/Fedora/Directory.cs
@@ -1,4 +1,5 @@
-using System;
+using Fedora.ApiModel;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -8,6 +9,10 @@ namespace Fedora
{
public class Directory : Resource
{
+ public Directory(FedoraJsonLdResponse jsonLdResponse) : base(jsonLdResponse)
+ {
+ }
+
public required List Directories { get; set; } = new List();
public required List Files { get; set; } = new List();
}
diff --git a/LeedsExperiment/Fedora/File.cs b/LeedsExperiment/Fedora/File.cs
index 69fa0c8..358bc86 100644
--- a/LeedsExperiment/Fedora/File.cs
+++ b/LeedsExperiment/Fedora/File.cs
@@ -1,15 +1,16 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using Fedora.ApiModel;
namespace Fedora
{
public class File : Resource
{
+ public File(FedoraJsonLdResponse jsonLdResponse) : base(jsonLdResponse)
+ {
+ }
- public string Origin { get
+ public string Origin
+ {
+ get
{
return "origin";
}
diff --git a/LeedsExperiment/Fedora/IFedora.cs b/LeedsExperiment/Fedora/IFedora.cs
index 6261948..cb1cc43 100644
--- a/LeedsExperiment/Fedora/IFedora.cs
+++ b/LeedsExperiment/Fedora/IFedora.cs
@@ -1,15 +1,18 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using Fedora.ApiModel;
namespace Fedora
{
public interface IFedora
{
Task Proxy(string contentType, string path, string? jsonLdMode = null, bool preferContained = false);
- Task CreateArchivalGroup(string parentPath, string slug, string name);
+ Task CreateArchivalGroup(string parentPath, string slug, string name, Transaction? transaction = null);
+
+ // Transactions
+ Task BeginTransaction();
+ Task CheckTransaction(Transaction tx);
+ Task KeepTransactionAlive(Transaction tx);
+ Task CommitTransaction(Transaction tx);
+ Task RollbackTransaction(Transaction tx);
}
}
diff --git a/LeedsExperiment/Fedora/RequestX.cs b/LeedsExperiment/Fedora/RequestX.cs
index 7bf7e41..b0e1d52 100644
--- a/LeedsExperiment/Fedora/RequestX.cs
+++ b/LeedsExperiment/Fedora/RequestX.cs
@@ -5,6 +5,7 @@
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
+using Fedora.ApiModel;
using Fedora.Vocab;
namespace Fedora
@@ -26,11 +27,21 @@ public static HttpRequestMessage WithContainedDescriptions(this HttpRequestMessa
return requestMessage;
}
+ public static HttpRequestMessage InTransaction(this HttpRequestMessage requestMessage, Transaction? transaction)
+ {
+ if(transaction != null)
+ {
+ requestMessage.Headers.Add(Transaction.HeaderName, transaction.Location.ToString());
+ }
+ return requestMessage;
+ }
+
public static HttpRequestMessage WithName(this HttpRequestMessage requestMessage, string? name)
{
if(requestMessage.Content == null && !string.IsNullOrWhiteSpace(name))
{
- requestMessage.Content = new StringContent($"PREFIX dc: <> dc:title \"{name}\"");
+ var turtle = MediaTypeHeaderValue.Parse("text/turtle");
+ requestMessage.Content = new StringContent($"PREFIX dc: <> dc:title \"{name}\"", turtle);
}
return requestMessage;
}
@@ -40,5 +51,11 @@ public static HttpRequestMessage WithSlug(this HttpRequestMessage requestMessage
requestMessage.Headers.Add("slug", slug);
return requestMessage;
}
+
+ public static HttpRequestMessage AsArchivalGroup(this HttpRequestMessage requestMessage)
+ {
+ requestMessage.Headers.Add("Link", $"<{RepositoryTypes.ArchivalGroup}>;rel=\"type\"");
+ return requestMessage;
+ }
}
}
diff --git a/LeedsExperiment/Fedora/Resource.cs b/LeedsExperiment/Fedora/Resource.cs
index 156c02b..b0a73e4 100644
--- a/LeedsExperiment/Fedora/Resource.cs
+++ b/LeedsExperiment/Fedora/Resource.cs
@@ -1,18 +1,34 @@
-using System;
+using Fedora.ApiModel;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace Fedora
{
public abstract class Resource
{
+ public Resource(FedoraJsonLdResponse jsonLdResponse)
+ {
+ Name = jsonLdResponse.Title;
+ Created = jsonLdResponse.Created;
+ CreatedBy = jsonLdResponse.CreatedBy;
+ LastModified = jsonLdResponse.LastModified;
+ LastModifiedBy = jsonLdResponse.LastModifiedBy;
+ }
+
// The original name of the resource (possibly non-filesystem-safe)
// Use dc:title on the fedora resource
public string? Name { get; set; }
// The Fedora identifier
public required string Identifier { get; set; }
+
+ public DateTime? Created { get; set; }
+ public string? CreatedBy { get; set; }
+ public DateTime? LastModified { get; set; }
+ public string? LastModifiedBy { get; set; }
}
}
diff --git a/LeedsExperiment/Preservation.API/Controllers/FedoraController.cs b/LeedsExperiment/Preservation.API/Controllers/FedoraController.cs
index 60923f1..4506bb3 100644
--- a/LeedsExperiment/Preservation.API/Controllers/FedoraController.cs
+++ b/LeedsExperiment/Preservation.API/Controllers/FedoraController.cs
@@ -33,7 +33,6 @@ public async Task Index(string contentTypeMajor, string contentTy
var result = await fedora.Proxy(contentType, fullPath, jsonLdMode, contained);
return Content(result, contentType);
}
-
- public
+
}
}
diff --git a/LeedsExperiment/Preservation/FedoraWrapper.cs b/LeedsExperiment/Preservation/FedoraWrapper.cs
index a318a92..240b351 100644
--- a/LeedsExperiment/Preservation/FedoraWrapper.cs
+++ b/LeedsExperiment/Preservation/FedoraWrapper.cs
@@ -43,35 +43,129 @@ public async Task Proxy(string contentType, string path, string? jsonLdM
}
- public async Task CreateArchivalGroup(string parentPath, string slug, string name)
+ public async Task CreateArchivalGroup(string parentPath, string slug, string name, Transaction? transaction = null)
{
- var uri = new Uri(parentPath, UriKind.Relative);
- var req = MakeHttpRequestMessage(parentPath, HttpMethod.Post);
- // .ForJsonLd() // no? for POST?
-
- // Now uncomment these:
-
- // .WithName(name)
- // .WithSlug(slug);
- // req.Headers.Add("Link", $"<{RepositoryTypes.ArchivalGroup}>;rel=\"type\"");
+ var req = MakeHttpRequestMessage(parentPath, HttpMethod.Post)
+ .InTransaction(transaction)
+ .WithName(name)
+ .WithSlug(slug)
+ .AsArchivalGroup();
var response = await _httpClient.SendAsync(req);
response.EnsureSuccessStatusCode();
// The body is the new resource URL
- var newReq = MakeHttpRequestMessage(response.Headers.Location!, HttpMethod.Get).ForJsonLd();
+ var newReq = MakeHttpRequestMessage(response.Headers.Location!, HttpMethod.Get)
+ .ForJsonLd();
var newResponse = await _httpClient.SendAsync(newReq);
var archivalGroupResponse = await MakeFedoraResponse(newResponse);
- var ag = new ArchivalGroup
+ if(archivalGroupResponse != null)
{
- Name = archivalGroupResponse.Title,
- Identifier = archivalGroupResponse.Id,
- Directories = new List(),
- Files = new List()
- };
-
- // add the extra created/modified/by fields to Fedora.Resource
- return ag;
+ var archivalGroup = new ArchivalGroup(archivalGroupResponse)
+ {
+ Identifier = archivalGroupResponse.Id,
+ Directories = new List(),
+ Files = new List()
+ };
+
+ // add the extra created/modified/by fields to Fedora.Resource
+ return archivalGroup;
+ }
+ return null;
+ }
+
+ public async Task BeginTransaction()
+ {
+ var req = MakeHttpRequestMessage("fcr:tx", HttpMethod.Post);
+ var response = await _httpClient.SendAsync(req);
+ response.EnsureSuccessStatusCode();
+ var tx = new Transaction();
+ tx.Location = response.Headers.Location!;
+ if (response.Headers.TryGetValues("Atomic-Expires", out IEnumerable? values))
+ {
+ tx.Expires = DateTime.Parse(values.First());
+ }
+ return tx;
+ }
+
+ public async Task CheckTransaction(Transaction tx)
+ {
+ HttpRequestMessage req = MakeHttpRequestMessage(tx.Location, HttpMethod.Get);
+ var response = await _httpClient.SendAsync(req);
+ switch (response.StatusCode)
+ {
+ case System.Net.HttpStatusCode.NoContent:
+ tx.Expired = false;
+ break;
+ case System.Net.HttpStatusCode.NotFound:
+ // error?
+ break;
+ case System.Net.HttpStatusCode.Gone:
+ tx.Expired = true;
+ break;
+ default:
+ // error?
+ break;
+ }
+ }
+
+ public async Task KeepTransactionAlive(Transaction tx)
+ {
+ HttpRequestMessage req = MakeHttpRequestMessage(tx.Location, HttpMethod.Post);
+ var response = await _httpClient.SendAsync(req);
+ response.EnsureSuccessStatusCode();
+
+ if (response.Headers.TryGetValues("Atomic-Expires", out IEnumerable? values))
+ {
+ tx.Expires = DateTime.Parse(values.First());
+ }
+ }
+
+ public async Task CommitTransaction(Transaction tx)
+ {
+ HttpRequestMessage req = MakeHttpRequestMessage(tx.Location, HttpMethod.Put);
+ var response = await _httpClient.SendAsync(req);
+ switch (response.StatusCode)
+ {
+ case System.Net.HttpStatusCode.NoContent:
+ tx.Committed = true;
+ break;
+ case System.Net.HttpStatusCode.NotFound:
+ // error?
+ break;
+ case System.Net.HttpStatusCode.Conflict:
+ tx.Committed = false;
+ break;
+ case System.Net.HttpStatusCode.Gone:
+ tx.Expired = true;
+ break;
+ default:
+ // error?
+ break;
+ }
+ response.EnsureSuccessStatusCode();
+ }
+
+ public async Task RollbackTransaction(Transaction tx)
+ {
+ HttpRequestMessage req = MakeHttpRequestMessage(tx.Location, HttpMethod.Delete);
+ var response = await _httpClient.SendAsync(req);
+ switch (response.StatusCode)
+ {
+ case System.Net.HttpStatusCode.NoContent:
+ tx.RolledBack = true;
+ break;
+ case System.Net.HttpStatusCode.NotFound:
+ // error?
+ break;
+ case System.Net.HttpStatusCode.Gone:
+ tx.Expired = true;
+ break;
+ default:
+ // error?
+ break;
+ }
+ response.EnsureSuccessStatusCode();
}
private HttpRequestMessage MakeHttpRequestMessage(string path, HttpMethod method)
@@ -98,4 +192,5 @@ private HttpRequestMessage MakeHttpRequestMessage(Uri uri, HttpMethod method)
}
return fedoraResponse;
}
+
}
diff --git a/LeedsExperiment/SamplesWorker/Worker.cs b/LeedsExperiment/SamplesWorker/Worker.cs
index 0b9c1b5..92c903f 100644
--- a/LeedsExperiment/SamplesWorker/Worker.cs
+++ b/LeedsExperiment/SamplesWorker/Worker.cs
@@ -37,7 +37,7 @@ private async Task EntryPoint(string[] args, CancellationToken stoppingToken)
private async Task DoDefault()
{
// Console.WriteLine("supply something");
- await fedora.CreateArchivalGroup("ag-root", Now(), "But same title");
+ await fedora.CreateArchivalGroup("ag-demo-root", Now(), "This is the title");
}
private async Task OcflV1()
@@ -45,6 +45,7 @@ private async Task OcflV1()
var path = @"C:\Users\TomCrane\Dropbox\digirati\leeds\fedora-experiments\versioned-example\working\v1";
// begin transaction
+ var transaction = await fedora.BeginTransaction();
// make an archival group named Now()