diff --git a/src/OrasProject.Oras/Content/Extensions.cs b/src/OrasProject.Oras/Content/Extensions.cs
index 3a0b544..230361c 100644
--- a/src/OrasProject.Oras/Content/Extensions.cs
+++ b/src/OrasProject.Oras/Content/Extensions.cs
@@ -31,7 +31,7 @@ public static class Extensions
///
///
///
- public static async Task> SuccessorsAsync(this IFetchable fetcher, Descriptor node, CancellationToken cancellationToken)
+ public static async Task> GetSuccessorsAsync(this IFetchable fetcher, Descriptor node, CancellationToken cancellationToken)
{
switch (node.MediaType)
{
@@ -61,7 +61,7 @@ public static async Task> SuccessorsAsync(this IFetchable fetc
return index.Manifests;
}
}
- return new List();
+ return Array.Empty();
}
///
diff --git a/src/OrasProject.Oras/Content/IPredecessorFinder.cs b/src/OrasProject.Oras/Content/IPredecessorFinder.cs
new file mode 100644
index 0000000..ac6192e
--- /dev/null
+++ b/src/OrasProject.Oras/Content/IPredecessorFinder.cs
@@ -0,0 +1,36 @@
+// Copyright The ORAS Authors.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using OrasProject.Oras.Oci;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OrasProject.Oras.Content;
+
+///
+/// IPredecessorFinder finds out the nodes directly pointing to a given node of a
+/// directed acyclic graph.
+/// In other words, returns the "parents" of the current descriptor.
+/// IPredecessorFinder is an extension of Storage.
+///
+public interface IPredecessorFinder
+{
+ ///
+ /// returns the nodes directly pointing to the current node.
+ ///
+ ///
+ ///
+ ///
+ Task> GetPredecessorsAsync(Descriptor node, CancellationToken cancellationToken = default);
+}
diff --git a/src/OrasProject.Oras/Content/MemoryGraph.cs b/src/OrasProject.Oras/Content/MemoryGraph.cs
new file mode 100644
index 0000000..ee1f5c2
--- /dev/null
+++ b/src/OrasProject.Oras/Content/MemoryGraph.cs
@@ -0,0 +1,65 @@
+// Copyright The ORAS Authors.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using OrasProject.Oras.Oci;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OrasProject.Oras.Content;
+
+internal class MemoryGraph : IPredecessorFinder
+{
+ private readonly ConcurrentDictionary> _predecessors = new();
+
+ ///
+ /// Returns the nodes directly pointing to the current node.
+ ///
+ ///
+ ///
+ public Task> GetPredecessorsAsync(Descriptor node, CancellationToken cancellationToken = default)
+ {
+ var key = node.BasicDescriptor;
+ if (_predecessors.TryGetValue(key, out var predecessors))
+ {
+ return Task.FromResult>(predecessors.Values);
+ }
+ return Task.FromResult>(Array.Empty());
+ }
+
+ internal async Task IndexAsync(IFetchable fetcher, Descriptor node, CancellationToken cancellationToken)
+ {
+ var successors = await fetcher.GetSuccessorsAsync(node, cancellationToken).ConfigureAwait(false);
+ Index(node, successors);
+ }
+
+ ///
+ /// Index indexes predecessors for each direct successor of the given node.
+ /// There is no data consistency issue as long as deletion is not implemented
+ /// for the underlying storage.
+ ///
+ ///
+ ///
+ private void Index(Descriptor node, IEnumerable successors)
+ {
+ var predecessorKey = node.BasicDescriptor;
+ foreach (var successor in successors)
+ {
+ var successorKey = successor.BasicDescriptor;
+ var predecessors = _predecessors.GetOrAdd(successorKey, _ => new ConcurrentDictionary());
+ predecessors.TryAdd(predecessorKey, node);
+ }
+ }
+}
diff --git a/src/OrasProject.Oras/Content/MemoryStorage.cs b/src/OrasProject.Oras/Content/MemoryStorage.cs
new file mode 100644
index 0000000..c733169
--- /dev/null
+++ b/src/OrasProject.Oras/Content/MemoryStorage.cs
@@ -0,0 +1,55 @@
+// Copyright The ORAS Authors.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using OrasProject.Oras.Exceptions;
+using OrasProject.Oras.Oci;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OrasProject.Oras.Content;
+
+internal class MemoryStorage : IStorage
+{
+ private readonly ConcurrentDictionary _content = new();
+
+ public Task ExistsAsync(Descriptor target, CancellationToken _ = default)
+ {
+ return Task.FromResult(_content.ContainsKey(target.BasicDescriptor));
+ }
+
+ public Task FetchAsync(Descriptor target, CancellationToken _ = default)
+ {
+ if (!_content.TryGetValue(target.BasicDescriptor, out var content))
+ {
+ throw new NotFoundException($"{target.Digest}: {target.MediaType}");
+ }
+ return Task.FromResult(new MemoryStream(content));
+ }
+
+ public async Task PushAsync(Descriptor expected, Stream contentStream, CancellationToken cancellationToken = default)
+ {
+ var key = expected.BasicDescriptor;
+ if (_content.ContainsKey(key))
+ {
+ throw new AlreadyExistsException($"{expected.Digest}: {expected.MediaType}");
+ }
+
+ var content = await contentStream.ReadAllAsync(expected, cancellationToken).ConfigureAwait(false);
+ if (!_content.TryAdd(key, content))
+ {
+ throw new AlreadyExistsException($"{key.Digest}: {key.MediaType}");
+ }
+ }
+}
diff --git a/src/OrasProject.Oras/Content/MemoryStore.cs b/src/OrasProject.Oras/Content/MemoryStore.cs
new file mode 100644
index 0000000..37a5b63
--- /dev/null
+++ b/src/OrasProject.Oras/Content/MemoryStore.cs
@@ -0,0 +1,97 @@
+// Copyright The ORAS Authors.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using OrasProject.Oras.Exceptions;
+using OrasProject.Oras.Oci;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OrasProject.Oras.Content;
+
+public class MemoryStore : ITarget, IPredecessorFinder
+{
+ private readonly MemoryStorage _storage = new();
+ private readonly MemoryTagStore _tagResolver = new();
+ private readonly MemoryGraph _graph = new();
+
+ ///
+ /// ExistsAsync returns checks if the described content exists.
+ ///
+ ///
+ ///
+ ///
+ public async Task ExistsAsync(Descriptor target, CancellationToken cancellationToken = default)
+ => await _storage.ExistsAsync(target, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// FetchAsync fetches the content identified by the descriptor.
+ ///
+ ///
+ ///
+ ///
+ public async Task FetchAsync(Descriptor target, CancellationToken cancellationToken = default)
+ => await _storage.FetchAsync(target, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// PushAsync pushes the content, matching the expected descriptor.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task PushAsync(Descriptor expected, Stream contentStream, CancellationToken cancellationToken = default)
+ {
+ await _storage.PushAsync(expected, contentStream, cancellationToken).ConfigureAwait(false);
+ await _graph.IndexAsync(_storage, expected, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// ResolveAsync resolves a reference to a descriptor.
+ ///
+ ///
+ ///
+ ///
+ public async Task ResolveAsync(string reference, CancellationToken cancellationToken = default)
+ => await _tagResolver.ResolveAsync(reference, cancellationToken).ConfigureAwait(false);
+
+ ///
+ /// TagAsync tags a descriptor with a reference string.
+ /// It throws NotFoundException if the tagged content does not exist.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task TagAsync(Descriptor descriptor, string reference, CancellationToken cancellationToken = default)
+ {
+ if (!await _storage.ExistsAsync(descriptor, cancellationToken).ConfigureAwait(false))
+ {
+ throw new NotFoundException($"{descriptor.Digest}: {descriptor.MediaType}");
+ }
+ await _tagResolver.TagAsync(descriptor, reference, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// PredecessorsAsync returns the nodes directly pointing to the current node.
+ /// Predecessors returns null without error if the node does not exists in the
+ /// store.
+ ///
+ ///
+ ///
+ ///
+ public async Task> GetPredecessorsAsync(Descriptor node, CancellationToken cancellationToken = default)
+ => await _graph.GetPredecessorsAsync(node, cancellationToken).ConfigureAwait(false);
+}
diff --git a/src/OrasProject.Oras/Content/MemoryTagStore.cs b/src/OrasProject.Oras/Content/MemoryTagStore.cs
new file mode 100644
index 0000000..6ba12d0
--- /dev/null
+++ b/src/OrasProject.Oras/Content/MemoryTagStore.cs
@@ -0,0 +1,40 @@
+// Copyright The ORAS Authors.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using OrasProject.Oras.Exceptions;
+using OrasProject.Oras.Oci;
+using System.Collections.Concurrent;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OrasProject.Oras.Content;
+
+internal class MemoryTagStore : ITagStore
+{
+ private readonly ConcurrentDictionary _index = new();
+
+ public Task ResolveAsync(string reference, CancellationToken _ = default)
+ {
+ if (!_index.TryGetValue(reference, out var content))
+ {
+ throw new NotFoundException();
+ }
+ return Task.FromResult(content);
+ }
+
+ public Task TagAsync(Descriptor descriptor, string reference, CancellationToken _ = default)
+ {
+ _index.AddOrUpdate(reference, descriptor, (_, _) => descriptor);
+ return Task.CompletedTask;
+ }
+}
diff --git a/src/OrasProject.Oras/Copy.cs b/src/OrasProject.Oras/Copy.cs
deleted file mode 100644
index da22acf..0000000
--- a/src/OrasProject.Oras/Copy.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright The ORAS Authors.
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using OrasProject.Oras.Interfaces;
-using OrasProject.Oras.Oci;
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using static OrasProject.Oras.Content.Extensions;
-
-namespace OrasProject.Oras
-{
- public class Copy
- {
-
- ///
- /// Copy copies a rooted directed acyclic graph (DAG) with the tagged root node
- /// in the source Target to the destination Target.
- /// The destination reference will be the same as the source reference if the
- /// destination reference is left blank.
- /// Returns the descriptor of the root node on successful copy.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static async Task CopyAsync(ITarget src, string srcRef, ITarget dst, string dstRef, CancellationToken cancellationToken)
- {
- if (src is null)
- {
- throw new Exception("null source target");
- }
- if (dst is null)
- {
- throw new Exception("null destination target");
- }
- if (dstRef == string.Empty)
- {
- dstRef = srcRef;
- }
- var root = await src.ResolveAsync(srcRef, cancellationToken);
- await CopyGraphAsync(src, dst, root, cancellationToken);
- await dst.TagAsync(root, dstRef, cancellationToken);
- return root;
- }
-
- public static async Task CopyGraphAsync(ITarget src, ITarget dst, Descriptor node, CancellationToken cancellationToken)
- {
- // check if node exists in target
- if (!await dst.ExistsAsync(node, cancellationToken))
- {
- // retrieve successors
- var successors = await src.SuccessorsAsync(node, cancellationToken);
- // obtain data stream
- var dataStream = await src.FetchAsync(node, cancellationToken);
- // check if the node has successors
- if (successors != null)
- {
- foreach (var childNode in successors)
- {
- await CopyGraphAsync(src, dst, childNode, cancellationToken);
- }
- }
- await dst.PushAsync(node, dataStream, cancellationToken);
- }
- }
- }
-}
diff --git a/src/OrasProject.Oras/Extensions.cs b/src/OrasProject.Oras/Extensions.cs
new file mode 100644
index 0000000..505f7ff
--- /dev/null
+++ b/src/OrasProject.Oras/Extensions.cs
@@ -0,0 +1,73 @@
+// Copyright The ORAS Authors.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using OrasProject.Oras.Oci;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using static OrasProject.Oras.Content.Extensions;
+
+namespace OrasProject.Oras;
+
+public static class Extensions
+{
+
+ ///
+ /// Copy copies a rooted directed acyclic graph (DAG) with the tagged root node
+ /// in the source Target to the destination Target.
+ /// The destination reference will be the same as the source reference if the
+ /// destination reference is left blank.
+ /// Returns the descriptor of the root node on successful copy.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task CopyAsync(this ITarget src, string srcRef, ITarget dst, string dstRef, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(dstRef))
+ {
+ dstRef = srcRef;
+ }
+ var root = await src.ResolveAsync(srcRef, cancellationToken).ConfigureAwait(false);
+ await src.CopyGraphAsync(dst, root, cancellationToken).ConfigureAwait(false);
+ await dst.TagAsync(root, dstRef, cancellationToken).ConfigureAwait(false);
+ return root;
+ }
+
+ public static async Task CopyGraphAsync(this ITarget src, ITarget dst, Descriptor node, CancellationToken cancellationToken)
+ {
+ // check if node exists in target
+ if (await dst.ExistsAsync(node, cancellationToken).ConfigureAwait(false))
+ {
+ return;
+ }
+
+ // retrieve successors
+ var successors = await src.GetSuccessorsAsync(node, cancellationToken).ConfigureAwait(false);
+ // obtain data stream
+ var dataStream = await src.FetchAsync(node, cancellationToken).ConfigureAwait(false);
+ // check if the node has successors
+ if (successors != null)
+ {
+ foreach (var childNode in successors)
+ {
+ await src.CopyGraphAsync(dst, childNode, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ await dst.PushAsync(node, dataStream, cancellationToken).ConfigureAwait(false);
+ }
+}
diff --git a/src/OrasProject.Oras/Interfaces/IReadOnlyTarget.cs b/src/OrasProject.Oras/IReadOnlyTarget.cs
similarity index 74%
rename from src/OrasProject.Oras/Interfaces/IReadOnlyTarget.cs
rename to src/OrasProject.Oras/IReadOnlyTarget.cs
index db9d675..22446ba 100644
--- a/src/OrasProject.Oras/Interfaces/IReadOnlyTarget.cs
+++ b/src/OrasProject.Oras/IReadOnlyTarget.cs
@@ -13,12 +13,11 @@
using OrasProject.Oras.Content;
-namespace OrasProject.Oras.Interfaces
+namespace OrasProject.Oras;
+
+///
+/// IReadOnlyTarget represents a read-only Target.
+///
+public interface IReadOnlyTarget : IReadOnlyStorage, IResolvable
{
- ///
- /// IReadOnlyTarget represents a read-only Target.
- ///
- public interface IReadOnlyTarget : IReadOnlyStorage, IResolvable
- {
- }
}
diff --git a/src/OrasProject.Oras/Interfaces/ITarget.cs b/src/OrasProject.Oras/ITarget.cs
similarity index 77%
rename from src/OrasProject.Oras/Interfaces/ITarget.cs
rename to src/OrasProject.Oras/ITarget.cs
index 8bfa739..d6fa4f3 100644
--- a/src/OrasProject.Oras/Interfaces/ITarget.cs
+++ b/src/OrasProject.Oras/ITarget.cs
@@ -13,12 +13,11 @@
using OrasProject.Oras.Content;
-namespace OrasProject.Oras.Interfaces
+namespace OrasProject.Oras;
+
+///
+/// Target is a CAS with generic tags
+///
+public interface ITarget : IStorage, ITagStore
{
- ///
- /// Target is a CAS with generic tags
- ///
- public interface ITarget : IStorage, ITagStore
- {
- }
}
diff --git a/src/OrasProject.Oras/Memory/MemoryGraph.cs b/src/OrasProject.Oras/Memory/MemoryGraph.cs
deleted file mode 100644
index f2193ef..0000000
--- a/src/OrasProject.Oras/Memory/MemoryGraph.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright The ORAS Authors.
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using OrasProject.Oras.Content;
-using OrasProject.Oras.Oci;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using static OrasProject.Oras.Content.Extensions;
-
-namespace OrasProject.Oras.Memory
-{
- internal class MemoryGraph
- {
- private ConcurrentDictionary> _predecessors = new ConcurrentDictionary>();
-
- internal async Task IndexAsync(IFetchable fetcher, Descriptor node, CancellationToken cancellationToken)
- {
- IList successors = await fetcher.SuccessorsAsync(node, cancellationToken);
- Index(node, successors, cancellationToken);
- }
-
- ///
- /// PredecessorsAsync returns the nodes directly pointing to the current node.
- /// Predecessors returns null without error if the node does not exists in the
- /// store.
- ///
- ///
- ///
- ///
- internal async Task> PredecessorsAsync(Descriptor node, CancellationToken cancellationToken)
- {
- var key = node.BasicDescriptor;
- if (!this._predecessors.TryGetValue(key, out ConcurrentDictionary predecessors))
- {
- return default;
- }
- var res = predecessors.Values.ToList();
- return await Task.FromResult(res);
- }
-
- ///
- /// Index indexes predecessors for each direct successor of the given node.
- /// There is no data consistency issue as long as deletion is not implemented
- /// for the underlying storage.
- ///
- ///
- ///
- ///
- private void Index(Descriptor node, IList successors, CancellationToken cancellationToken)
- {
- if (successors is null || successors.Count == 0)
- {
- return;
- }
-
- var predecessorKey = node.BasicDescriptor;
- foreach (var successor in successors)
- {
- var successorKey = successor.BasicDescriptor;
- var predecessors = this._predecessors.GetOrAdd(successorKey, new ConcurrentDictionary());
- predecessors.TryAdd(predecessorKey, node);
- }
-
- }
- }
-}
diff --git a/src/OrasProject.Oras/Memory/MemoryStorage.cs b/src/OrasProject.Oras/Memory/MemoryStorage.cs
deleted file mode 100644
index ea95ece..0000000
--- a/src/OrasProject.Oras/Memory/MemoryStorage.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright The ORAS Authors.
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using OrasProject.Oras.Content;
-using OrasProject.Oras.Exceptions;
-using OrasProject.Oras.Oci;
-using System.Collections.Concurrent;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using static OrasProject.Oras.Content.Extensions;
-
-namespace OrasProject.Oras.Memory
-{
- internal class MemoryStorage : IStorage
- {
- private ConcurrentDictionary _content = new ConcurrentDictionary();
-
- public Task ExistsAsync(Descriptor target, CancellationToken cancellationToken)
- {
- var contentExist = _content.ContainsKey(target.BasicDescriptor);
- return Task.FromResult(contentExist);
- }
-
-
-
- public Task FetchAsync(Descriptor target, CancellationToken cancellationToken = default)
- {
- var contentExist = this._content.TryGetValue(target.BasicDescriptor, out byte[] content);
- if (!contentExist)
- {
- throw new NotFoundException($"{target.Digest} : {target.MediaType}");
- }
- return Task.FromResult(new MemoryStream(content));
- }
-
-
- public async Task PushAsync(Descriptor expected, Stream contentStream, CancellationToken cancellationToken = default)
- {
- var key = expected.BasicDescriptor;
- var contentExist = _content.TryGetValue(key, out byte[] _);
- if (contentExist)
- {
- throw new AlreadyExistsException($"{expected.Digest} : {expected.MediaType}");
- }
- var readBytes = await contentStream.ReadAllAsync(expected, cancellationToken);
-
- var added = _content.TryAdd(key, readBytes);
- if (!added) throw new AlreadyExistsException($"{key.Digest} : {key.MediaType}");
- return;
- }
- }
-}
diff --git a/src/OrasProject.Oras/Memory/MemoryTagResolver.cs b/src/OrasProject.Oras/Memory/MemoryTagResolver.cs
deleted file mode 100644
index 8ac589e..0000000
--- a/src/OrasProject.Oras/Memory/MemoryTagResolver.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright The ORAS Authors.
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using OrasProject.Oras.Content;
-using OrasProject.Oras.Exceptions;
-using OrasProject.Oras.Oci;
-using System.Collections.Concurrent;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace OrasProject.Oras.Memory
-{
- internal class MemoryTagResolver : ITagStore
- {
-
- private ConcurrentDictionary _index = new ConcurrentDictionary();
- public Task ResolveAsync(string reference, CancellationToken cancellationToken = default)
- {
-
- var contentExist = _index.TryGetValue(reference, out Descriptor content);
- if (!contentExist)
- {
- throw new NotFoundException();
- }
- return Task.FromResult(content);
- }
-
- public Task TagAsync(Descriptor descriptor, string reference, CancellationToken cancellationToken = default)
- {
- _index.AddOrUpdate(reference, descriptor, (key, oldValue) => descriptor);
- return Task.CompletedTask;
- }
- }
-}
diff --git a/src/OrasProject.Oras/Memory/MemoryTarget.cs b/src/OrasProject.Oras/Memory/MemoryTarget.cs
deleted file mode 100644
index 1a4bdc1..0000000
--- a/src/OrasProject.Oras/Memory/MemoryTarget.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright The ORAS Authors.
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using OrasProject.Oras.Exceptions;
-using OrasProject.Oras.Interfaces;
-using OrasProject.Oras.Oci;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace OrasProject.Oras.Memory
-{
- public class MemoryTarget : ITarget
- {
- private MemoryStorage _storage = new MemoryStorage();
- private MemoryTagResolver _tagResolver = new MemoryTagResolver();
- private MemoryGraph _graph = new MemoryGraph();
-
- ///
- /// ExistsAsync returns checks if the described content exists.
- ///
- ///
- ///
- ///
- public async Task ExistsAsync(Descriptor target, CancellationToken cancellationToken = default)
- {
- return await _storage.ExistsAsync(target, cancellationToken);
- }
-
- ///
- /// FetchAsync fetches the content identified by the descriptor.
- ///
- ///
- ///
- ///
- public async Task FetchAsync(Descriptor target, CancellationToken cancellationToken = default)
- {
- return await _storage.FetchAsync(target, cancellationToken);
- }
-
- ///
- /// PushAsync pushes the content, matching the expected descriptor.
- ///
- ///
- ///
- ///
- ///
- public async Task PushAsync(Descriptor expected, Stream contentStream, CancellationToken cancellationToken = default)
- {
- await _storage.PushAsync(expected, contentStream, cancellationToken);
- await _graph.IndexAsync(_storage, expected, cancellationToken);
- }
-
- ///
- /// ResolveAsync resolves a reference to a descriptor.
- ///
- ///
- ///
- ///
- public async Task ResolveAsync(string reference, CancellationToken cancellationToken = default)
- {
- return await _tagResolver.ResolveAsync(reference, cancellationToken);
- }
-
- ///
- /// TagAsync tags a descriptor with a reference string.
- /// It throws NotFoundException if the tagged content does not exist.
- ///
- ///
- ///
- ///
- ///
- ///
- public async Task TagAsync(Descriptor descriptor, string reference, CancellationToken cancellationToken = default)
- {
-
- var exists = await _storage.ExistsAsync(descriptor, cancellationToken);
-
- if (!exists)
- {
- throw new NotFoundException($"{descriptor.Digest} : {descriptor.MediaType}");
- }
- await _tagResolver.TagAsync(descriptor, reference, cancellationToken);
-
- }
-
- ///
- /// PredecessorsAsync returns the nodes directly pointing to the current node.
- /// Predecessors returns null without error if the node does not exists in the
- /// store.
- ///
- ///
- ///
- ///
- public async Task> PredecessorsAsync(Descriptor node, CancellationToken cancellationToken = default)
- {
- return await _graph.PredecessorsAsync(node, cancellationToken);
- }
- }
-}
diff --git a/tests/OrasProject.Oras.Tests/CopyTest.cs b/tests/OrasProject.Oras.Tests/CopyTest.cs
index b4dc607..ecfec2c 100644
--- a/tests/OrasProject.Oras.Tests/CopyTest.cs
+++ b/tests/OrasProject.Oras.Tests/CopyTest.cs
@@ -12,7 +12,6 @@
// limitations under the License.
using OrasProject.Oras.Content;
-using OrasProject.Oras.Memory;
using OrasProject.Oras.Oci;
using System.Text;
using System.Text.Json;
@@ -30,7 +29,7 @@ public class CopyTest
[Fact]
public async Task CanCopyBetweenMemoryTargetsWithTaggedNode()
{
- var sourceTarget = new MemoryTarget();
+ var sourceTarget = new MemoryStore();
var cancellationToken = new CancellationToken();
var blobs = new List();
var descs = new List();
@@ -70,8 +69,8 @@ public async Task CanCopyBetweenMemoryTargetsWithTaggedNode()
var root = descs[3];
var reference = "foobar";
await sourceTarget.TagAsync(root, reference, cancellationToken);
- var destinationTarget = new MemoryTarget();
- var gotDesc = await Copy.CopyAsync(sourceTarget, reference, destinationTarget, "", cancellationToken);
+ var destinationTarget = new MemoryStore();
+ var gotDesc = await sourceTarget.CopyAsync(reference, destinationTarget, "", cancellationToken);
Assert.Equal(gotDesc, root);
Assert.Equal(await destinationTarget.ResolveAsync(reference, cancellationToken), root);
@@ -93,7 +92,7 @@ public async Task CanCopyBetweenMemoryTargetsWithTaggedNode()
[Fact]
public async Task CanCopyBetweenMemoryTargets()
{
- var sourceTarget = new MemoryTarget();
+ var sourceTarget = new MemoryStore();
var cancellationToken = new CancellationToken();
var blobs = new List();
var descs = new List();
@@ -130,8 +129,8 @@ public async Task CanCopyBetweenMemoryTargets()
}
var root = descs[3];
- var destinationTarget = new MemoryTarget();
- await Copy.CopyGraphAsync(sourceTarget, destinationTarget, root, cancellationToken);
+ var destinationTarget = new MemoryStore();
+ await sourceTarget.CopyGraphAsync(destinationTarget, root, cancellationToken);
for (var i = 0; i < descs.Count; i++)
{
Assert.True(await destinationTarget.ExistsAsync(descs[i], cancellationToken));
diff --git a/tests/OrasProject.Oras.Tests/MemoryTest/MemoryTargetTest.cs b/tests/OrasProject.Oras.Tests/MemoryTest/MemoryTargetTest.cs
index cbc66a8..57bddab 100644
--- a/tests/OrasProject.Oras.Tests/MemoryTest/MemoryTargetTest.cs
+++ b/tests/OrasProject.Oras.Tests/MemoryTest/MemoryTargetTest.cs
@@ -13,7 +13,6 @@
using OrasProject.Oras.Content;
using OrasProject.Oras.Exceptions;
-using OrasProject.Oras.Memory;
using OrasProject.Oras.Oci;
using System.Text;
using System.Text.Json;
@@ -41,7 +40,7 @@ public async Task CanStoreData()
};
var reference = "foobar";
- var memoryTarget = new MemoryTarget();
+ var memoryTarget = new MemoryStore();
var cancellationToken = new CancellationToken();
var stream = new MemoryStream(content);
await memoryTarget.PushAsync(descriptor, stream, cancellationToken);
@@ -76,7 +75,7 @@ public async Task ThrowsNotFoundExceptionWhenDataIsNotAvailable()
Size = content.Length
};
- var memoryTarget = new MemoryTarget();
+ var memoryTarget = new MemoryStore();
var cancellationToken = new CancellationToken();
var contentExists = await memoryTarget.ExistsAsync(descriptor, cancellationToken);
Assert.False(contentExists);
@@ -102,7 +101,7 @@ public async Task ThrowsAlreadyExistsExceptionWhenSameDataIsPushedTwice()
Size = content.Length
};
- var memoryTarget = new MemoryTarget();
+ var memoryTarget = new MemoryStore();
var cancellationToken = new CancellationToken();
var stream = new MemoryStream(content);
await memoryTarget.PushAsync(descriptor, stream, cancellationToken);
@@ -126,7 +125,7 @@ public async Task ThrowsAnErrorWhenABadPushOccurs()
Size = content.Length
};
- var memoryTarget = new MemoryTarget();
+ var memoryTarget = new MemoryStore();
var cancellationToken = new CancellationToken();
var stream = new MemoryStream(wrongContent);
await Assert.ThrowsAnyAsync(async () =>
@@ -153,7 +152,7 @@ public async Task ThrowsMismatchedDigestExceptionWhenHashInDigestIsDifferentFrom
Size = content.Length
};
- var memoryTarget = new MemoryTarget();
+ var memoryTarget = new MemoryStore();
var cancellationToken = new CancellationToken();
var stream = new MemoryStream(wrongContent);
await Assert.ThrowsAnyAsync(async () =>
@@ -169,7 +168,7 @@ await Assert.ThrowsAnyAsync(async () =>
[Fact]
public async Task ShouldReturnPredecessorsOfNodes()
{
- var memoryTarget = new MemoryTarget();
+ var memoryTarget = new MemoryStore();
var cancellationToken = new CancellationToken();
var blobs = new List();
var descs = new List();
@@ -229,18 +228,18 @@ public async Task ShouldReturnPredecessorsOfNodes()
new() { descs[7] }, // blob 4
new() { descs[7] }, // blob 5
new() { descs[8] }, // blob 6
- null!, // blob 7
- null! // blob 8
+ new() { }, // blob 7
+ new() { } // blob 8
};
foreach (var (i, want) in wants.Select((v, i) => (i, v)))
{
- var predecessors = await memoryTarget.PredecessorsAsync(descs[i], cancellationToken);
- if (predecessors is null && want is null) continue;
+ var predecessors = await memoryTarget.GetPredecessorsAsync(descs[i], cancellationToken);
want.Sort((a, b) => (int)b.Size - (int)a.Size);
- predecessors?.Sort((a, b) => (int)b.Size - (int)a.Size);
- Assert.Equal(predecessors, want);
+ var predecessorList = predecessors?.ToList();
+ predecessorList?.Sort((a, b) => (int)b.Size - (int)a.Size);
+ Assert.Equal(predecessorList, want);
}
}
}
diff --git a/tests/OrasProject.Oras.Tests/RemoteTest/RepositoryTest.cs b/tests/OrasProject.Oras.Tests/RemoteTest/RepositoryTest.cs
index be8f626..472ef3e 100644
--- a/tests/OrasProject.Oras.Tests/RemoteTest/RepositoryTest.cs
+++ b/tests/OrasProject.Oras.Tests/RemoteTest/RepositoryTest.cs
@@ -15,7 +15,6 @@
using Moq.Protected;
using OrasProject.Oras.Content;
using OrasProject.Oras.Exceptions;
-using OrasProject.Oras.Memory;
using OrasProject.Oras.Oci;
using OrasProject.Oras.Remote;
using System.Collections.Immutable;
@@ -1929,7 +1928,7 @@ public async Task ManifestStore_TagAsync()
}
if (req.Content?.Headers?.ContentLength != null)
{
- var buf = new byte[req.Content.Headers.ContentLength.Value];
+ var buf = new byte[req.Content.Headers.ContentLength.Value];
(await req.Content.ReadAsByteArrayAsync()).CopyTo(buf, 0);
gotIndex = buf;
}
@@ -2102,9 +2101,9 @@ public async Task CopyFromRepositoryToMemory()
reg.HttpClient = CustomClient(func);
var src = await reg.Repository("source", CancellationToken.None);
- var dst = new MemoryTarget();
+ var dst = new MemoryStore();
var tagName = "latest";
- var desc = await Copy.CopyAsync(src, tagName, dst, tagName, CancellationToken.None);
+ var desc = await src.CopyAsync(tagName, dst, tagName, CancellationToken.None);
}
[Fact]