Skip to content

Commit 17334b3

Browse files
Resource update (#44)
* add some required apis to harmony * Add tests for resource deletion functionality - Implemented tests to verify that local and remote resources are correctly deleted from the resource service. - Ensured that deleted resources are no longer retrievable from all APIs. * fix issue trying to have 2 transactions on the same db at once * return early if there are no pending resources to upload --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 5a660d1 commit 17334b3

File tree

8 files changed

+85
-16
lines changed

8 files changed

+85
-16
lines changed

src/SIL.Harmony.Core/IRemoteResourceService.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ public interface IRemoteResourceService
1515
/// <param name="localResourceCachePath">path defined by the CRDT config where the resource should be stored</param>
1616
/// <returns>download result containing the path to the downloaded file, this is stored in the local db and not synced</returns>
1717
Task<DownloadResult> DownloadResource(string remoteId, string localResourceCachePath);
18+
1819
/// <summary>
1920
/// upload a resource to the remote server
2021
/// </summary>
22+
/// <param name="resourceId">id of the resource in the CRDT</param>
2123
/// <param name="localPath">full path to the resource on the local machine</param>
2224
/// <returns>an upload result with the remote id, the id will be stored and transmitted to other clients so they can also download the resource</returns>
23-
Task<UploadResult> UploadResource(string localPath);
25+
Task<UploadResult> UploadResource(Guid resourceId, string localPath);
2426
}
2527

2628
public record DownloadResult(string LocalPath);

src/SIL.Harmony.Tests/ResourceTests/RemoteResourcesTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,38 @@ public async Task CanGetAResourceGivenAnId()
207207
(await _resourceService.GetResource(localAndRemoteResource.Id)).Should().BeEquivalentTo(localAndRemoteResource);
208208
(await _resourceService.GetResource(Guid.NewGuid())).Should().BeNull();
209209
}
210+
211+
[Fact]
212+
public async Task DeleteResource_RemovesLocalResource()
213+
{
214+
// Arrange: create a local resource
215+
var (resourceId, localPath) = await SetupLocalFile("delete-local");
216+
(await _resourceService.GetResource(resourceId)).Should().NotBeNull();
217+
(await _resourceService.GetLocalResource(resourceId)).Should().NotBeNull();
218+
219+
// Act: delete the resource
220+
await _resourceService.DeleteResource(_localClientId, resourceId);
221+
222+
// Assert: resource is gone from all APIs
223+
(await _resourceService.GetResource(resourceId)).Should().BeNull();
224+
(await _resourceService.GetLocalResource(resourceId)).Should().BeNull();
225+
(await _resourceService.AllResources()).Should().NotContain(r => r.Id == resourceId);
226+
}
227+
228+
[Fact]
229+
public async Task DeleteResource_RemovesRemoteResource()
230+
{
231+
// Arrange: create a remote resource
232+
var (resourceId, remoteId) = await SetupRemoteResource("delete-remote");
233+
(await _resourceService.GetResource(resourceId)).Should().NotBeNull();
234+
(await _resourceService.GetLocalResource(resourceId)).Should().BeNull();
235+
236+
// Act: delete the resource
237+
await _resourceService.DeleteResource(_localClientId, resourceId);
238+
239+
// Assert: resource is gone from all APIs
240+
(await _resourceService.GetResource(resourceId)).Should().BeNull();
241+
(await _resourceService.GetLocalResource(resourceId)).Should().BeNull();
242+
(await _resourceService.AllResources()).Should().NotContain(r => r.Id == resourceId);
243+
}
210244
}

src/SIL.Harmony.Tests/ResourceTests/RemoteServiceMock.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public Task<DownloadResult> DownloadResource(string remoteId, string localResour
2626

2727
private readonly Queue<string> _throwOnUpload = new();
2828

29-
public async Task<UploadResult> UploadResource(string localPath)
29+
public async Task<UploadResult> UploadResource(Guid resourceId, string localPath)
3030
{
3131
await Task.Yield();//yield back to the scheduler to emulate how exceptions are thrown
3232
if (_throwOnUpload.TryPeek(out var throwOnUpload))

src/SIL.Harmony/Db/CrdtRepository.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public async Task<T> Execute<T>(Func<CrdtRepository, Task<T>> func)
3030
await using var repo = await CreateRepository();
3131
return await func(repo);
3232
}
33+
public async Task Execute(Func<CrdtRepository, Task> func)
34+
{
35+
await using var repo = await CreateRepository();
36+
await func(repo);
37+
}
3338

3439
public async ValueTask<T> Execute<T>(Func<CrdtRepository, ValueTask<T>> func)
3540
{
@@ -390,6 +395,11 @@ public async Task AddLocalResource(LocalResource localResource)
390395
await _dbContext.SaveChangesAsync();
391396
}
392397

398+
public async Task DeleteLocalResource(Guid id)
399+
{
400+
await _dbContext.Set<LocalResource>().Where(r => r.Id == id).ExecuteDeleteAsync();
401+
}
402+
393403
public IAsyncEnumerable<LocalResource> LocalResourcesByIds(IEnumerable<Guid> resourceIds)
394404
{
395405
return _dbContext.Set<LocalResource>().Where(r => resourceIds.Contains(r.Id)).AsAsyncEnumerable();

src/SIL.Harmony/Resource/CreateRemoteResourceChange.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace SIL.Harmony.Resource;
55

6-
public class CreateRemoteResourceChange(Guid resourceId, string remoteId) : CreateChange<RemoteResource>(resourceId), IPolyType
6+
public class CreateRemoteResourceChange(Guid entityId, string remoteId) : CreateChange<RemoteResource>(entityId), IPolyType
77
{
88
public string RemoteId { get; set; } = remoteId;
99
public override ValueTask<RemoteResource> NewEntity(Commit commit, IChangeContext context)

src/SIL.Harmony/Resource/CreateRemoteResourcePendingUpload.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33

44
namespace SIL.Harmony.Resource;
55

6-
public class CreateRemoteResourcePendingUploadChange: CreateChange<RemoteResource>, IPolyType
6+
public class CreateRemoteResourcePendingUploadChange(Guid entityId)
7+
: CreateChange<RemoteResource>(entityId), IPolyType
78
{
8-
public CreateRemoteResourcePendingUploadChange(Guid resourceId) : base(resourceId)
9-
{
10-
}
11-
129
public override ValueTask<RemoteResource> NewEntity(Commit commit, IChangeContext context)
1310
{
1411
return ValueTask.FromResult(new RemoteResource
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
13
namespace SIL.Harmony.Resource;
24

35
public class HarmonyResource
46
{
57
public required Guid Id { get; init; }
68
public string? RemoteId { get; init; }
79
public string? LocalPath { get; init; }
10+
[MemberNotNullWhen(true, nameof(LocalPath))]
811
public bool Local => !string.IsNullOrEmpty(LocalPath);
12+
[MemberNotNullWhen(true, nameof(RemoteId))]
913
public bool Remote => !string.IsNullOrEmpty(RemoteId);
10-
}
14+
}

src/SIL.Harmony/ResourceService.cs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,42 @@ private void ValidateResourcesSetup()
2828
if (!_crdtConfig.Value.RemoteResourcesEnabled) throw new RemoteResourceNotEnabledException();
2929
}
3030

31+
public async Task AddExistingRemoteResource(string resourcePath,
32+
Guid clientId,
33+
Guid resourceId, string remoteId)
34+
{
35+
ValidateResourcesSetup();
36+
var localResource = new LocalResource
37+
{
38+
Id = resourceId,
39+
LocalPath = Path.GetFullPath(resourcePath)
40+
};
41+
if (!localResource.FileExists()) throw new FileNotFoundException(localResource.LocalPath);
42+
43+
await _dataModel.AddChange(clientId, new CreateRemoteResourceChange(localResource.Id, remoteId));
44+
await using var repo = await _crdtRepositoryFactory.CreateRepository();
45+
await repo.AddLocalResource(localResource);
46+
}
47+
3148
public async Task<HarmonyResource> AddLocalResource(string resourcePath,
3249
Guid clientId,
3350
Guid id = default,
3451
IRemoteResourceService? resourceService = null)
3552
{
3653
ValidateResourcesSetup();
37-
await using var repo = await _crdtRepositoryFactory.CreateRepository();
3854
var localResource = new LocalResource
3955
{
4056
Id = id == default ? Guid.NewGuid() : id,
4157
LocalPath = Path.GetFullPath(resourcePath)
4258
};
4359
if (!localResource.FileExists()) throw new FileNotFoundException(localResource.LocalPath);
44-
await using var transaction = await repo.BeginTransactionAsync();
45-
await repo.AddLocalResource(localResource);
4660
UploadResult? uploadResult = null;
4761
if (resourceService is not null)
4862
{
4963
try
5064
{
5165

52-
uploadResult = await resourceService.UploadResource(localResource.LocalPath);
66+
uploadResult = await resourceService.UploadResource(localResource.Id, localResource.LocalPath);
5367
}
5468
catch (Exception e)
5569
{
@@ -66,7 +80,7 @@ public async Task<HarmonyResource> AddLocalResource(string resourcePath,
6680
await _dataModel.AddChange(clientId, new CreateRemoteResourcePendingUploadChange(localResource.Id));
6781
}
6882

69-
await transaction.CommitAsync();
83+
await _crdtRepositoryFactory.Execute(repo => repo.AddLocalResource(localResource));
7084
return new HarmonyResource
7185
{
7286
Id = localResource.Id,
@@ -88,12 +102,13 @@ public async Task UploadPendingResources(Guid clientId, IRemoteResourceService r
88102
{
89103
ValidateResourcesSetup();
90104
var pendingUploads = await ListResourcesPendingUpload();
105+
if (pendingUploads is []) return;
91106
var changes = new List<IChange>(pendingUploads.Length);
92107
try
93108
{
94109
foreach (var localResource in pendingUploads)
95110
{
96-
var uploadResult = await remoteResourceService.UploadResource(localResource.LocalPath);
111+
var uploadResult = await remoteResourceService.UploadResource(localResource.Id, localResource.LocalPath);
97112
changes.Add(new RemoteResourceUploadedChange(localResource.Id, uploadResult.RemoteId));
98113
}
99114
}
@@ -116,7 +131,7 @@ public async Task UploadPendingResource(Guid resourceId, Guid clientId, IRemoteR
116131
public async Task UploadPendingResource(LocalResource localResource, Guid clientId, IRemoteResourceService remoteResourceService)
117132
{
118133
ValidateResourcesSetup();
119-
var uploadResult = await remoteResourceService.UploadResource(localResource.LocalPath);
134+
var uploadResult = await remoteResourceService.UploadResource(localResource.Id, localResource.LocalPath);
120135
await _dataModel.AddChange(clientId, new RemoteResourceUploadedChange(localResource.Id, uploadResult.RemoteId));
121136
}
122137

@@ -196,4 +211,11 @@ private async Task<IEnumerable<HarmonyResource>> AllResourcesInternal()
196211
var resources = await AllResourcesInternal();
197212
return resources.FirstOrDefault(r => r.Id == resourceId);
198213
}
214+
215+
public async Task DeleteResource(Guid clientId, Guid resourceId)
216+
{
217+
await _dataModel.AddChange(clientId, new DeleteChange<RemoteResource>(resourceId));
218+
await using var repo = await _crdtRepositoryFactory.CreateRepository();
219+
await repo.DeleteLocalResource(resourceId);
220+
}
199221
}

0 commit comments

Comments
 (0)