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]