2
2
using Ipfs ;
3
3
using Ipfs . CoreApi ;
4
4
using OwlCore . ComponentModel ;
5
+ using OwlCore . Extensions ;
5
6
using OwlCore . Storage ;
6
7
7
8
namespace OwlCore . Kubo . Cache ;
@@ -14,16 +15,18 @@ namespace OwlCore.Kubo.Cache;
14
15
/// </remarks>
15
16
public class CachedNameApi : SettingsBase , INameApi , IDelegable < INameApi > , IFlushable
16
17
{
18
+ private readonly SemaphoreSlim _cacheUpdateMutex = new ( 1 , 1 ) ;
19
+
17
20
/// <summary>
18
21
/// The cached record for a published path name in a <see cref="CachedNameApi"/>.
19
22
/// </summary>
20
23
public record PublishedPathName ( string path , bool resolve , string key , TimeSpan ? lifetime , NamedContent returnValue ) ;
21
-
24
+
22
25
/// <summary>
23
26
/// The cached record for a published cid name in a <see cref="CachedNameApi"/>.
24
27
/// </summary>
25
28
public record PublishedCidName ( Cid id , string key , TimeSpan ? lifetime , NamedContent returnValue ) ;
26
-
29
+
27
30
/// <summary>
28
31
/// The cached record for a resolved name in a <see cref="CachedNameApi"/>.
29
32
/// </summary>
@@ -80,73 +83,82 @@ public List<PublishedPathName> PublishedStringNamedContent
80
83
/// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
81
84
public async Task FlushAsync ( CancellationToken cancellationToken = default )
82
85
{
83
- foreach ( var item in PublishedCidNamedContent . ToArray ( ) )
86
+ using ( await _cacheUpdateMutex . DisposableWaitAsync ( cancellationToken ) )
84
87
{
85
- cancellationToken . ThrowIfCancellationRequested ( ) ;
88
+ foreach ( var item in PublishedCidNamedContent )
89
+ {
90
+ cancellationToken . ThrowIfCancellationRequested ( ) ;
86
91
87
- Console . WriteLine ( $ "Flushing key { item . key } with value { item . id } ") ;
92
+ Console . WriteLine ( $ "Flushing key { item . key } with value { item . id } ") ;
88
93
89
- // Publish to ipfs
90
- var result = await Inner . PublishAsync ( item . id , item . key , item . lifetime , cancellationToken ) ;
94
+ // Publish to ipfs
95
+ var result = await Inner . PublishAsync ( item . id , item . key , item . lifetime , cancellationToken ) ;
91
96
92
- // Verify result matches original returned data
93
- _ = Guard . Equals ( result . ContentPath , item . returnValue . ContentPath ) ;
94
- _ = Guard . Equals ( result . NamePath , item . returnValue . NamePath ) ;
97
+ // Verify result matches original returned data
98
+ _ = Guard . Equals ( result . ContentPath , item . returnValue . ContentPath ) ;
99
+ _ = Guard . Equals ( result . NamePath , item . returnValue . NamePath ) ;
95
100
96
- // Update cache
97
- PublishedCidNamedContent . Remove ( item ) ;
98
- PublishedCidNamedContent . Add ( item with { returnValue = result } ) ;
99
- }
101
+ // Update cache
102
+ PublishedCidNamedContent . Remove ( item ) ;
103
+ PublishedCidNamedContent . Add ( item with { returnValue = result } ) ;
104
+ }
100
105
101
- foreach ( var item in PublishedStringNamedContent )
102
- {
103
- cancellationToken . ThrowIfCancellationRequested ( ) ;
106
+ foreach ( var item in PublishedStringNamedContent )
107
+ {
108
+ cancellationToken . ThrowIfCancellationRequested ( ) ;
104
109
105
- Console . WriteLine ( $ "Flushing key { item . key } with value { item . path } ") ;
110
+ Console . WriteLine ( $ "Flushing key { item . key } with value { item . path } ") ;
106
111
107
- // Publish to ipfs
108
- var result = await Inner . PublishAsync ( item . path , item . resolve , item . key , item . lifetime , cancellationToken ) ;
112
+ // Publish to ipfs
113
+ var result = await Inner . PublishAsync ( item . path , item . resolve , item . key , item . lifetime , cancellationToken ) ;
109
114
110
- // Verify result matches original returned data
111
- _ = Guard . Equals ( result . ContentPath , item . returnValue . ContentPath ) ;
112
- _ = Guard . Equals ( result . NamePath , item . returnValue . NamePath ) ;
115
+ // Verify result matches original returned data
116
+ _ = Guard . Equals ( result . ContentPath , item . returnValue . ContentPath ) ;
117
+ _ = Guard . Equals ( result . NamePath , item . returnValue . NamePath ) ;
113
118
114
- // Update cache
115
- PublishedStringNamedContent . Remove ( item ) ;
116
- PublishedStringNamedContent . Add ( item with { returnValue = result } ) ;
119
+ // Update cache
120
+ PublishedStringNamedContent . Remove ( item ) ;
121
+ PublishedStringNamedContent . Add ( item with { returnValue = result } ) ;
122
+ }
117
123
}
118
124
}
119
125
120
126
/// <inheritdoc />
121
127
public async Task < NamedContent > PublishAsync ( string path , bool resolve = true , string key = "self" , TimeSpan ? lifetime = null , CancellationToken cancel = default )
122
128
{
123
- if ( PublishedStringNamedContent . FirstOrDefault ( x => x . key == key ) is { } existing )
124
- PublishedStringNamedContent . Remove ( existing ) ;
129
+ using ( await _cacheUpdateMutex . DisposableWaitAsync ( cancel ) )
130
+ {
131
+ if ( PublishedStringNamedContent . FirstOrDefault ( x => x . key == key ) is { } existing )
132
+ PublishedStringNamedContent . Remove ( existing ) ;
125
133
126
- var keys = await KeyApi . ListAsync ( cancel ) ;
127
- var existingKey = keys . FirstOrDefault ( x => x . Name == key ) ;
128
- var keyId = existingKey ? . Id ;
134
+ var keys = await KeyApi . ListAsync ( cancel ) ;
135
+ var existingKey = keys . FirstOrDefault ( x => x . Name == key ) ;
136
+ var keyId = existingKey ? . Id ;
129
137
130
- NamedContent published = new ( ) { ContentPath = path , NamePath = $ "/ipns/{ keyId } " } ;
138
+ NamedContent published = new ( ) { ContentPath = path , NamePath = $ "/ipns/{ keyId } " } ;
131
139
132
- PublishedStringNamedContent . Add ( new ( path , resolve , key , lifetime , published ) ) ;
133
- return published ;
140
+ PublishedStringNamedContent . Add ( new ( path , resolve , key , lifetime , published ) ) ;
141
+ return published ;
142
+ }
134
143
}
135
144
136
145
/// <inheritdoc />
137
146
public async Task < NamedContent > PublishAsync ( Cid id , string key = "self" , TimeSpan ? lifetime = null , CancellationToken cancel = default )
138
147
{
139
- if ( PublishedCidNamedContent . FirstOrDefault ( x => x . key == key ) is { } existing )
140
- PublishedCidNamedContent . Remove ( existing ) ;
148
+ using ( await _cacheUpdateMutex . DisposableWaitAsync ( cancel ) )
149
+ {
150
+ if ( PublishedCidNamedContent . FirstOrDefault ( x => x . key == key ) is { } existing )
151
+ PublishedCidNamedContent . Remove ( existing ) ;
141
152
142
- var keys = await KeyApi . ListAsync ( cancel ) ;
143
- var existingKey = keys . FirstOrDefault ( x => x . Name == key ) ;
144
- var keyId = existingKey ? . Id ;
153
+ var keys = await KeyApi . ListAsync ( cancel ) ;
154
+ var existingKey = keys . FirstOrDefault ( x => x . Name == key ) ;
155
+ var keyId = existingKey ? . Id ;
145
156
146
- NamedContent published = new ( ) { ContentPath = $ "/ipfs/{ id } ", NamePath = $ "/ipns/{ keyId } " } ;
147
- PublishedCidNamedContent . Add ( new ( id , key , lifetime , published ) ) ;
157
+ NamedContent published = new ( ) { ContentPath = $ "/ipfs/{ id } ", NamePath = $ "/ipns/{ keyId } " } ;
158
+ PublishedCidNamedContent . Add ( new ( id , key , lifetime , published ) ) ;
148
159
149
- return published ;
160
+ return published ;
161
+ }
150
162
}
151
163
152
164
/// <inheritdoc />
@@ -157,49 +169,52 @@ public async Task<NamedContent> PublishAsync(Cid id, string key = "self", TimeSp
157
169
/// </remarks>
158
170
public async Task < string > ResolveAsync ( string name , bool recursive = false , bool nocache = false , CancellationToken cancel = default )
159
171
{
160
- if ( nocache )
172
+ using ( await _cacheUpdateMutex . DisposableWaitAsync ( cancel ) )
161
173
{
162
- try
174
+ if ( nocache )
163
175
{
164
- // Don't resolve with cache, but still save resolved data to cache.
165
- var resToCache = await Inner . ResolveAsync ( name , recursive , nocache , cancel ) ;
166
-
167
- var existing = ResolvedNames . FirstOrDefault ( x => x . name == name ) ;
168
- if ( existing is not null )
169
- ResolvedNames . Remove ( existing ) ;
170
-
171
- ResolvedNames . Add ( new ( name , recursive , resToCache ) ) ;
172
-
173
- return resToCache ;
176
+ try
177
+ {
178
+ // Don't resolve with cache, but still save resolved data to cache.
179
+ var resToCache = await Inner . ResolveAsync ( name , recursive , nocache , cancel ) ;
180
+
181
+ var existing = ResolvedNames . FirstOrDefault ( x => x . name == name ) ;
182
+ if ( existing is not null )
183
+ ResolvedNames . Remove ( existing ) ;
184
+
185
+ ResolvedNames . Add ( new ( name , recursive , resToCache ) ) ;
186
+
187
+ return resToCache ;
188
+ }
189
+ catch
190
+ {
191
+ // request failed, continue with cache anyway
192
+ }
174
193
}
175
- catch
194
+
195
+ // Check if name is in published cache
196
+ if ( PublishedCidNamedContent . FirstOrDefault ( x => x . returnValue . NamePath is not null && ( name . Contains ( x . returnValue . NamePath ) || x . returnValue . NamePath . Contains ( name ) ) ) is { } publishedCidNamedContent )
176
197
{
177
- // request failed, continue with cache anyway
198
+ if ( publishedCidNamedContent . returnValue . ContentPath is not null )
199
+ return publishedCidNamedContent . returnValue . ContentPath ;
178
200
}
179
- }
180
201
181
- // Check if name is in published cache
182
- if ( PublishedCidNamedContent . FirstOrDefault ( x => x . returnValue . NamePath is not null && ( name . Contains ( x . returnValue . NamePath ) || x . returnValue . NamePath . Contains ( name ) ) ) is { } publishedCidNamedContent )
183
- {
184
- if ( publishedCidNamedContent . returnValue . ContentPath is not null )
185
- return publishedCidNamedContent . returnValue . ContentPath ;
186
- }
187
-
188
- // Check in other published cache
189
- if ( PublishedStringNamedContent . FirstOrDefault ( x => x . returnValue . NamePath is not null && ( name . Contains ( x . returnValue . NamePath ) || x . returnValue . NamePath . Contains ( name ) ) ) is { } publishedStringNamedContent )
190
- {
191
- if ( publishedStringNamedContent . returnValue . ContentPath is not null )
192
- return publishedStringNamedContent . returnValue . ContentPath ;
193
- }
202
+ // Check in other published cache
203
+ if ( PublishedStringNamedContent . FirstOrDefault ( x => x . returnValue . NamePath is not null && ( name . Contains ( x . returnValue . NamePath ) || x . returnValue . NamePath . Contains ( name ) ) ) is { } publishedStringNamedContent )
204
+ {
205
+ if ( publishedStringNamedContent . returnValue . ContentPath is not null )
206
+ return publishedStringNamedContent . returnValue . ContentPath ;
207
+ }
194
208
195
- // Check if previously resolved.
196
- if ( ResolvedNames . FirstOrDefault ( x => x . name == name ) is { } resolvedName )
197
- return resolvedName . returnValue ;
209
+ // Check if previously resolved.
210
+ if ( ResolvedNames . FirstOrDefault ( x => x . name == name ) is { } resolvedName )
211
+ return resolvedName . returnValue ;
198
212
199
- // If not, resolve the name and cache.
200
- var result = await Inner . ResolveAsync ( name , recursive , nocache , cancel ) ;
201
- ResolvedNames . Add ( new ( name , recursive , result ) ) ;
213
+ // If not, resolve the name and cache.
214
+ var result = await Inner . ResolveAsync ( name , recursive , nocache , cancel ) ;
215
+ ResolvedNames . Add ( new ( name , recursive , result ) ) ;
202
216
203
- return result ;
217
+ return result ;
218
+ }
204
219
}
205
220
}
0 commit comments