diff --git a/README.md b/README.md index c719657..1b06bbe 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ building upon `Microsoft.Extensions.FileProviders`. * [Ramstack.FileProviders](#ramstackfileproviders-2) * [PrefixedFileProvider](#prefixedfileprovider) * [SubFileProvider](#subfileprovider) - * [ZipFileProvider](#zipfileprovider) * [Ramstack.FileProviders.Globbing](#ramstackfileprovidersglobbing-1) * [Ramstack.FileProviders.Extensions](#ramstackfileprovidersextensions-1) * [Ramstack.FileProviders.Composition](#ramstackfileproviderscomposition-1) @@ -45,7 +44,7 @@ dotnet add package Ramstack.FileProviders.Extensions ``` ### Ramstack.FileProviders -Provides additional implementations of `IFileProvider` including `PrefixedFileProvider`, `SubFileProvider`, and `ZipFileProvider`. +Provides additional implementations of `IFileProvider` including `PrefixedFileProvider` and `SubFileProvider`. To install the `Ramstack.FileProviders` [NuGet package](https://www.nuget.org/packages/Ramstack.FileProviders) in your project, run the following command: @@ -80,7 +79,6 @@ This library offers additional implementations of the [IFileProvider](https://le - `SubFileProvider` - `PrefixedFileProvider` -- `ZipFileProvider` #### PrefixedFileProvider @@ -150,17 +148,6 @@ IFileInfo file = provider.GetFileInfo("/README"); Console.WriteLine(file.Exists); ``` -#### ZipFileProvider - -`ZipFileProvider` enables access to files within ZIP archives as if they were part of the file system. - -Example: -```csharp -IFileProvider provider = new ZipFileProvider("/path/to/archive.zip"); -foreach (IFileInfo file in provider.GetDirectoryContents("/")) - Console.WriteLine(file.Name); -``` - ### Ramstack.FileProviders.Globbing `GlobbingFileProvider` class filters files using include and/or exclude glob patterns. Include patterns make only matching files visible, @@ -290,15 +277,15 @@ var changeToken = compositeFileProvider.Watch("**/*.json").Flatten(); ## NuGet Packages - [Ramstack.FileProviders.Extensions](https://www.nuget.org/packages/Ramstack.FileProviders.Extensions) — Useful and convenient extensions for `IFileProvider`, bringing its capabilities and experience closer to what's provided by the `DirectoryInfo` and `FileInfo` classes. -- [Ramstack.FileProviders](https://www.nuget.org/packages/Ramstack.FileProviders) — Additional file providers, including `ZipFileProvider`, `PrefixedFileProvider`, and `SubFileProvider`. +- [Ramstack.FileProviders](https://www.nuget.org/packages/Ramstack.FileProviders) — Additional file providers, including `PrefixedFileProvider` and `SubFileProvider`. - [Ramstack.FileProviders.Globbing](https://www.nuget.org/packages/Ramstack.FileProviders.Globbing) — A file provider that filters files using include and/or exclude glob patterns. Include patterns make only matching files visible, while exclude patterns hide specific files. Both include and exclude patterns can be combined for flexible file visibility control. - [Ramstack.FileProviders.Composition](https://www.nuget.org/packages/Ramstack.FileProviders.Composition) — Provides a helper class for flattening and composing `IFileProvider`. ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileProviders.Composition/README.md b/src/Ramstack.FileProviders.Composition/README.md index dab4ca0..542469b 100644 --- a/src/Ramstack.FileProviders.Composition/README.md +++ b/src/Ramstack.FileProviders.Composition/README.md @@ -1,4 +1,6 @@ # Ramstack.FileProviders.Composition +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileProviders.Composition.svg)](https://nuget.org/packages/Ramstack.FileProviders.Composition) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.fileproviders)](https://github.com/rameel/ramstack.fileproviders/blob/main/LICENSE) Represents a .NET library that provides a helper class for flattening and composing `IFileProvider` instances. @@ -63,14 +65,14 @@ var changeToken = compositeFileProvider.Watch("**/*.json").Flatten(); ## Related Packages - [Ramstack.FileProviders.Extensions](https://www.nuget.org/packages/Ramstack.FileProviders.Extensions) — Useful and convenient extensions for `IFileProvider`, bringing its capabilities and experience closer to what's provided by the `DirectoryInfo` and `FileInfo` classes. -- [Ramstack.FileProviders](https://www.nuget.org/packages/Ramstack.FileProviders) — Additional file providers, including `ZipFileProvider`, `PrefixedFileProvider`, and `SubFileProvider`. +- [Ramstack.FileProviders](https://www.nuget.org/packages/Ramstack.FileProviders) — Additional file providers, including `PrefixedFileProvider` and `SubFileProvider`. - [Ramstack.FileProviders.Globbing](https://www.nuget.org/packages/Ramstack.FileProviders.Globbing) — A file provider that filters files using include and/or exclude glob patterns. Include patterns make only matching files visible, while exclude patterns hide specific files. Both include and exclude patterns can be combined for flexible file visibility control. ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileProviders.Extensions/README.md b/src/Ramstack.FileProviders.Extensions/README.md index 5e87a92..37a2f27 100644 --- a/src/Ramstack.FileProviders.Extensions/README.md +++ b/src/Ramstack.FileProviders.Extensions/README.md @@ -1,4 +1,6 @@ # Ramstack.FileProviders.Extensions +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileProviders.Extensions.svg)](https://nuget.org/packages/Ramstack.FileProviders.Extensions) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.fileproviders)](https://github.com/rameel/ramstack.fileproviders/blob/main/LICENSE) Represents a lightweight .NET library of useful and convenient extensions for `Microsoft.Extensions.FileProviders` that enhances file handling capabilities in .NET applications. @@ -66,16 +68,16 @@ foreach (FileNode file in provider.EnumerateFiles("/project", pattern: "**/*.md" ``` ## Related Packages -- [Ramstack.FileProviders](https://www.nuget.org/packages/Ramstack.FileProviders) — Additional file providers, including `ZipFileProvider`, `PrefixedFileProvider`, and `SubFileProvider`. +- [Ramstack.FileProviders](https://www.nuget.org/packages/Ramstack.FileProviders) — Additional file providers, including `PrefixedFileProvider` and `SubFileProvider`. - [Ramstack.FileProviders.Globbing](https://www.nuget.org/packages/Ramstack.FileProviders.Globbing) — A file provider that filters files using include and/or exclude glob patterns. Include patterns make only matching files visible, while exclude patterns hide specific files. Both include and exclude patterns can be combined for flexible file visibility control. - [Ramstack.FileProviders.Composition](https://www.nuget.org/packages/Ramstack.FileProviders.Composition) — Provides a helper class for flattening and composing `IFileProvider`. ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileProviders.Globbing/README.md b/src/Ramstack.FileProviders.Globbing/README.md index 635cfbe..4e4896b 100644 --- a/src/Ramstack.FileProviders.Globbing/README.md +++ b/src/Ramstack.FileProviders.Globbing/README.md @@ -1,4 +1,6 @@ # Ramstack.FileProviders.Globbing +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileProviders.Globbing.svg)](https://nuget.org/packages/Ramstack.FileProviders.Globbing) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.fileproviders)](https://github.com/rameel/ramstack.fileproviders/blob/main/LICENSE) Represents a .NET library implementing an `IFileProvider` that filters files using include and/or exclude glob patterns for flexible file visibility control. @@ -26,15 +28,15 @@ foreach (IFileInfo file in provider.GetDirectoryContents("/")) ## Related Packages - [Ramstack.FileProviders.Extensions](https://www.nuget.org/packages/Ramstack.FileProviders.Extensions) — Useful and convenient extensions for `IFileProvider`, bringing its capabilities and experience closer to what's provided by the `DirectoryInfo` and `FileInfo` classes. -- [Ramstack.FileProviders](https://www.nuget.org/packages/Ramstack.FileProviders) — Additional file providers, including `ZipFileProvider`, `PrefixedFileProvider`, and `SubFileProvider`. +- [Ramstack.FileProviders](https://www.nuget.org/packages/Ramstack.FileProviders) — Additional file providers, including `PrefixedFileProvider` and `SubFileProvider`. - [Ramstack.FileProviders.Composition](https://www.nuget.org/packages/Ramstack.FileProviders.Composition) — Provides a helper class for flattening and composing `IFileProvider`. ## Supported versions -| | Version | -|------|---------| -| .NET | 6, 7, 8 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileProviders/README.md b/src/Ramstack.FileProviders/README.md index 00e86db..5118256 100644 --- a/src/Ramstack.FileProviders/README.md +++ b/src/Ramstack.FileProviders/README.md @@ -1,9 +1,10 @@ # Ramstack.FileProviders +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileProviders.svg)](https://nuget.org/packages/Ramstack.FileProviders) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.fileproviders)](https://github.com/rameel/ramstack.fileproviders/blob/main/LICENSE) Represents a .NET library that provides additional implementations for `Microsoft.Extensions.FileProviders` including: - `PrefixedFileProvider` - `SubFileProvider` -- `ZipFileProvider` ## Getting Started @@ -81,16 +82,6 @@ IFileInfo file = provider.GetFileInfo("/README"); Console.WriteLine(file.Exists); ``` -## ZipFileProvider -`ZipFileProvider` enables access to files within ZIP archives as if they were part of the file system. - -Example: -```csharp -IFileProvider provider = new ZipFileProvider("/path/to/archive.zip"); -foreach (IFileInfo file in provider.GetDirectoryContents("/")) - Console.WriteLine(file.Name); -``` - ## Related Packages - [Ramstack.FileProviders.Extensions](https://www.nuget.org/packages/Ramstack.FileProviders.Extensions) — Useful and convenient extensions for `IFileProvider`, bringing its capabilities and experience closer to what's provided by the `DirectoryInfo` and `FileInfo` classes. - [Ramstack.FileProviders.Globbing](https://www.nuget.org/packages/Ramstack.FileProviders.Globbing) — A file provider that filters files using include and/or exclude glob patterns. Include patterns make only matching files visible, while exclude patterns hide specific files. Both include and exclude patterns can be combined for flexible file visibility control. @@ -98,9 +89,9 @@ foreach (IFileInfo file in provider.GetDirectoryContents("/")) ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs deleted file mode 100644 index c21425f..0000000 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System.IO.Compression; -using System.Runtime.CompilerServices; - -namespace Ramstack.FileProviders; - -/// -/// Provides access to files within a ZIP archive. -/// -/// -/// **WARNING:** -/// -/// This class is not thread-safe and is designed to handle only one file read operation at a time. -/// It does not support parallel or simultaneous opening of multiple files. -/// -/// -[Obsolete("Deprecated due to thread safety limitations and parallel file access capabilities.")] -public sealed class ZipFileProvider : IFileProvider, IDisposable -{ - private readonly ZipArchive _archive; - private readonly Dictionary _cache = - new() { ["/"] = new ZipDirectoryInfo("/") }; - - /// - /// Initializes a new instance of the class - /// using a ZIP archive located at the specified file path. - /// - /// The path to the ZIP archive file. - public ZipFileProvider(string path) - : this(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - } - - /// - /// Initializes a new instance of the class - /// using a stream containing a ZIP archive. - /// - /// The stream containing the ZIP archive. - /// to leave the stream open - /// after the object is disposed; otherwise, . - public ZipFileProvider(Stream stream, bool leaveOpen = false) - { - if (!stream.CanSeek) - throw new ArgumentException("Stream does not support seeking.", nameof(stream)); - - _archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen); - Initialize(_archive, _cache); - } - - /// - /// Initializes a new instance of the class - /// using an existing . - /// - /// The instance - /// to use for providing access to ZIP archive content. - public ZipFileProvider(ZipArchive archive) - { - if (archive.Mode != ZipArchiveMode.Read) - throw new ArgumentException( - "Archive must be opened in read mode (ZipArchiveMode.Read).", - nameof(archive)); - - _archive = archive; - Initialize(archive, _cache); - } - - /// - public IFileInfo GetFileInfo(string subpath) => - Find(subpath) ?? new NotFoundFileInfo(FilePath.GetFileName(subpath)); - - /// - public IDirectoryContents GetDirectoryContents(string subpath) => - Find(subpath) as IDirectoryContents ?? NotFoundDirectoryContents.Singleton; - - /// - public IChangeToken Watch(string filter) => - NullChangeToken.Singleton; - - /// - public void Dispose() => - _archive.Dispose(); - - private IFileInfo? Find(string path) => - _cache.GetValueOrDefault(FilePath.Normalize(path)); - - /// - /// Initializes the current provider by populating it with entries from the underlying ZIP archive. - /// - private static void Initialize(ZipArchive archive, Dictionary cache) - { - foreach (var entry in archive.Entries) - { - // - // Strip common path prefixes from zip entries to handle archives - // saved with absolute paths. - // - var path = FilePath.Normalize( - entry.FullName[GetPrefixLength(entry.FullName)..]); - - if (FilePath.HasTrailingSlash(entry.FullName)) - { - GetDirectory(path); - continue; - } - - var directory = GetDirectory(FilePath.GetDirectoryName(path)); - var file = new ZipFileInfo(FilePath.GetFileName(path), entry); - - // - // Archives legitimately may contain entries with identical names, - // so skip if a file with this name has already been added, - // avoiding duplicates in the directory file list. - // - if (cache.TryAdd(path, file)) - directory.RegisterFile(file); - } - - ZipDirectoryInfo GetDirectory(string path) - { - if (cache.TryGetValue(path, out var di)) - return (ZipDirectoryInfo)di; - - di = new ZipDirectoryInfo(FilePath.GetFileName(path)); - var parent = GetDirectory(FilePath.GetDirectoryName(path)); - parent.RegisterFile(di); - cache.Add(path, di); - - return (ZipDirectoryInfo)di; - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static int GetPrefixLength(string path) - { - // - // Check only well-known prefixes. - // Note: Since entry names can be arbitrary, - // we specifically target only common absolute path patterns. - // - - if (path.StartsWith(@"\\?\UNC\", StringComparison.OrdinalIgnoreCase) - || path.StartsWith(@"\\.\UNC\", StringComparison.OrdinalIgnoreCase) - || path.StartsWith("//?/UNC/", StringComparison.OrdinalIgnoreCase) - || path.StartsWith("//./UNC/", StringComparison.OrdinalIgnoreCase)) - return 8; - - if (path.StartsWith(@"\\?\", StringComparison.Ordinal) - || path.StartsWith(@"\\.\", StringComparison.Ordinal) - || path.StartsWith("//?/", StringComparison.Ordinal) - || path.StartsWith("//./", StringComparison.Ordinal)) - return path.Length >= 6 && IsAsciiLetter(path[4]) && path[5] == ':' ? 6 : 4; - - if (path.Length >= 2 - && IsAsciiLetter(path[0]) && path[1] == ':') - return 2; - - return 0; - - static bool IsAsciiLetter(char ch) => - (uint)((ch | 0x20) - 'a') <= 'z' - 'a'; - } - - #region Inner type: ZipDirectoryInfo - - /// - /// Represents directory contents and file information within a ZIP archive for the specified path. - /// This class is used to provide both and interfaces for directory entries in the ZIP archive. - /// - /// The name of the directory, not including any path. - [DebuggerDisplay("{Name,nq}")] - [DebuggerTypeProxy(typeof(ZipDirectoryInfoDebuggerProxy))] - private sealed class ZipDirectoryInfo(string name) : IDirectoryContents, IFileInfo - { - /// - /// The list of the within this directory. - /// - private readonly List _files = []; - - /// - public bool Exists => true; - - /// - public long Length => -1; - - /// - public string? PhysicalPath => null; - - /// - public string Name => name; - - /// - public DateTimeOffset LastModified => default; - - /// - public bool IsDirectory => true; - - /// - public Stream CreateReadStream() => - throw new NotSupportedException("Cannot create a read stream for a directory."); - - /// - public IEnumerator GetEnumerator() => - _files.AsEnumerable().GetEnumerator(); - - /// - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => - GetEnumerator(); - - /// - /// Register a file associated with this directory. - /// - /// The file associated with this directory. - public void RegisterFile(IFileInfo file) => - _files.Add(file); - } - - #endregion - - #region Inner type: ZipFileInfo - - /// - /// Represents a file within a ZIP archive as an implementation of the interface. - /// - /// The name of the file, not including any path. - /// The ZIP archive entry representing the file. - [DebuggerDisplay("{ToStringDebugger(),nq}")] - private sealed class ZipFileInfo(string name, ZipArchiveEntry entry) : IFileInfo - { - /// - public bool Exists => true; - - /// - public bool IsDirectory => false; - - /// - public DateTimeOffset LastModified => entry.LastWriteTime; - - /// - public long Length => entry.Length; - - /// - public string? PhysicalPath => null; - - /// - public string Name => name; - - /// - public Stream CreateReadStream() => - entry.Open(); - - private string ToStringDebugger() => - entry.FullName; - } - - #endregion - - #region Inner type: ZipDirectoryInfoDebuggerProxy - - /// - /// Represents a debugger proxy for viewing the contents of a instance in a more user-friendly way during debugging. - /// - /// The instance to provide debugging information for. - private sealed class ZipDirectoryInfoDebuggerProxy(ZipDirectoryInfo directoryInfo) - { - /// - /// Gets an array of instances representing the files within the associated . - /// - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public IFileInfo[] Files { get; } = directoryInfo.ToArray(); - } - - #endregion -} diff --git a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs deleted file mode 100644 index 4a3bfa6..0000000 --- a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System.IO.Compression; - -using Ramstack.FileProviders.Utilities; - -namespace Ramstack.FileProviders; - -[TestFixture] -public class ZipFileProviderTests : AbstractFileProviderTests -{ - private readonly TempFileStorage _storage = new TempFileStorage(); - private readonly string _path = - Path.Combine( - Path.GetTempPath(), - Path.GetRandomFileName() - ) + ".zip"; - - [OneTimeSetUp] - public void Setup() - { - ZipFile.CreateFromDirectory( - sourceDirectoryName: _storage.Root, - destinationArchiveFileName: _path, - compressionLevel: CompressionLevel.SmallestSize, - includeBaseDirectory: false); - } - - [OneTimeTearDown] - public void Cleanup() - { - _storage.Dispose(); - File.Delete(_path); - } - - [Test] - public void ZipArchive_WithIdenticalNameEntries() - { - using var provider = new ZipFileProvider(CreateArchive()); - - var list = provider - .EnumerateFiles("/1") - .ToArray(); - - Assert.That( - list.Length, - Is.EqualTo(1)); - - Assert.That( - list[0].ReadAllBytes(), - Is.EquivalentTo("Hello, World!"u8.ToArray())); - - static MemoryStream CreateArchive() - { - var stream = new MemoryStream(); - using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true)) - { - var a = archive.CreateEntry("1/text.txt"); - using (var writer = a.Open()) - writer.Write("Hello, World!"u8); - - archive.CreateEntry("1/text.txt"); - archive.CreateEntry(@"1\text.txt"); - } - - stream.Position = 0; - return stream; - } - } - - [Test] - public void ZipArchive_PrefixedEntries() - { - var archive = new ZipArchive(CreateArchive(), ZipArchiveMode.Read, leaveOpen: true); - using var provider = new ZipFileProvider(archive); - - var directories = provider - .EnumerateDirectories("/", "**") - .Select(f => - f.FullName) - .OrderBy(f => f) - .ToArray(); - - var files = provider - .EnumerateFiles("/", "**") - .Select(f => - f.FullName) - .OrderBy(f => f) - .ToArray(); - - Assert.That(files, Is.EquivalentTo( - [ - "/1/text.txt", - "/2/text.txt", - "/3/text.txt", - "/4/text.txt", - "/5/text.txt", - "/localhost/backup/text.txt", - "/localhost/share/text.txt", - "/server/backup/text.txt", - "/server/share/text.txt", - "/text.txt", - "/text.xml" - ])); - - Assert.That(directories, Is.EquivalentTo( - [ - "/1", - "/2", - "/3", - "/4", - "/5", - "/localhost", - "/localhost/backup", - "/localhost/share", - "/server", - "/server/backup", - "/server/share" - ])); - - static MemoryStream CreateArchive() - { - var stream = new MemoryStream(); - using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true)) - { - archive.CreateEntry(@"D:\1/text.txt"); - archive.CreateEntry(@"D:2\text.txt"); - - archive.CreateEntry(@"\\?\D:\text.txt"); - archive.CreateEntry(@"\\?\D:text.xml"); - archive.CreateEntry(@"\\.\D:\3\text.txt"); - archive.CreateEntry(@"//?/D:/4\text.txt"); - archive.CreateEntry(@"//./D:\5/text.txt"); - - archive.CreateEntry(@"\\?\UNC\localhost\share\text.txt"); - archive.CreateEntry(@"\\.\unc\server\share\text.txt"); - archive.CreateEntry(@"//?/UNC/localhost/backup\text.txt"); - archive.CreateEntry(@"//./unc/server/backup\text.txt"); - } - - stream.Position = 0; - return stream; - } - } - - [Test] - public void ZipArchive_Directories() - { - using var provider = new ZipFileProvider(CreateArchive()); - - var directories = provider - .EnumerateDirectories("/", "**") - .Select(f => - f.FullName) - .OrderBy(f => f) - .ToArray(); - - Assert.That(directories, Is.EquivalentTo( - [ - "/1", - "/2", - "/2/3", - "/4", - "/4/5", - "/4/5/6" - ])); - - static MemoryStream CreateArchive() - { - var stream = new MemoryStream(); - using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true)) - { - archive.CreateEntry(@"\1/"); - archive.CreateEntry(@"\2/"); - archive.CreateEntry(@"/2\"); - archive.CreateEntry(@"/2\"); - archive.CreateEntry(@"/2\"); - archive.CreateEntry(@"/2\3/"); - archive.CreateEntry(@"/2\3/"); - archive.CreateEntry(@"/2\3/"); - archive.CreateEntry(@"4\5/6\"); - } - - stream.Position = 0; - return stream; - } - } - - protected override IFileProvider GetFileProvider() => - new ZipFileProvider(_path); - - protected override DirectoryInfo GetDirectoryInfo() => - new DirectoryInfo(_storage.Root); - -}