From d280b361e7a097195a10566ebe43411f221ffc9b Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 17 Sep 2014 12:58:39 -0700 Subject: [PATCH] Refactoring IFileSystem Making AssemblyNeutral and creating .Interfaces package Changing semantics of string property to be relative Updating tests Changing return value to be always non-null with Exists property --- FileSystem.sln | 15 +- .../IDirectoryContents.cs | 21 ++ .../IExpirationTrigger.cs | 32 +++ .../IFileInfo.cs | 38 +++- .../IFileSystem.cs | 28 +++ ...rosoft.AspNet.FileSystems.Interfaces.kproj | 26 +++ .../project.json | 12 + .../EmbeddedResourceFileSystem.cs | 112 ++++++---- .../IFileSystem.cs | 37 ---- .../EnumerableDirectoryContents.cs | 33 +++ .../NotFoundDirectoryContents.cs | 31 +++ .../Implementation/NotFoundFileInfo.cs | 77 +++++++ .../PhysicalFileSystem.cs | 207 ++++++++++-------- src/Microsoft.AspNet.FileSystems/project.json | 33 ++- .../EmbeddedResourceFileSystemTests.cs | 123 ++++------- .../PhysicalFileSystemTests.cs | 203 ++++++++--------- 16 files changed, 636 insertions(+), 392 deletions(-) create mode 100644 src/Microsoft.AspNet.FileSystems.Interfaces/IDirectoryContents.cs create mode 100644 src/Microsoft.AspNet.FileSystems.Interfaces/IExpirationTrigger.cs rename src/{Microsoft.AspNet.FileSystems => Microsoft.AspNet.FileSystems.Interfaces}/IFileInfo.cs (53%) create mode 100644 src/Microsoft.AspNet.FileSystems.Interfaces/IFileSystem.cs create mode 100644 src/Microsoft.AspNet.FileSystems.Interfaces/Microsoft.AspNet.FileSystems.Interfaces.kproj create mode 100644 src/Microsoft.AspNet.FileSystems.Interfaces/project.json delete mode 100644 src/Microsoft.AspNet.FileSystems/IFileSystem.cs create mode 100644 src/Microsoft.AspNet.FileSystems/Implementation/EnumerableDirectoryContents.cs create mode 100644 src/Microsoft.AspNet.FileSystems/Implementation/NotFoundDirectoryContents.cs create mode 100644 src/Microsoft.AspNet.FileSystems/Implementation/NotFoundFileInfo.cs diff --git a/FileSystem.sln b/FileSystem.sln index 4475b12f..d4f71bd6 100644 --- a/FileSystem.sln +++ b/FileSystem.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22013.1 +VisualStudioVersion = 14.0.22108.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A1477614-E825-4204-A684-385004B63AEB}" EndProject @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json EndProjectSection EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.FileSystems.Interfaces", "src\Microsoft.AspNet.FileSystems.Interfaces\Microsoft.AspNet.FileSystems.Interfaces.kproj", "{DD94B7E8-3A59-4F84-98A0-8139BE259A87}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,6 +48,16 @@ Global {66FE5FDF-BBF9-4573-A7B7-53551731C0F9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {66FE5FDF-BBF9-4573-A7B7-53551731C0F9}.Release|Mixed Platforms.Build.0 = Release|Any CPU {66FE5FDF-BBF9-4573-A7B7-53551731C0F9}.Release|x86.ActiveCfg = Release|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Debug|x86.ActiveCfg = Debug|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Release|Any CPU.Build.0 = Release|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {DD94B7E8-3A59-4F84-98A0-8139BE259A87}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -53,5 +65,6 @@ Global GlobalSection(NestedProjects) = preSolution {A830B046-595A-4992-B9E1-3C28C6440707} = {A1477614-E825-4204-A684-385004B63AEB} {66FE5FDF-BBF9-4573-A7B7-53551731C0F9} = {E399495E-82B8-4C06-8779-C1D02BEF4495} + {DD94B7E8-3A59-4F84-98A0-8139BE259A87} = {A1477614-E825-4204-A684-385004B63AEB} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNet.FileSystems.Interfaces/IDirectoryContents.cs b/src/Microsoft.AspNet.FileSystems.Interfaces/IDirectoryContents.cs new file mode 100644 index 00000000..64b89d91 --- /dev/null +++ b/src/Microsoft.AspNet.FileSystems.Interfaces/IDirectoryContents.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.FileSystems +{ + /// + /// Represents a directory's content in the file system. + /// +#if ASPNET50 || ASPNETCORE50 + [Framework.Runtime.AssemblyNeutral] +#endif + public interface IDirectoryContents : IEnumerable + { + /// + /// True if a directory was located at the given path. + /// + bool Exists { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems.Interfaces/IExpirationTrigger.cs b/src/Microsoft.AspNet.FileSystems.Interfaces/IExpirationTrigger.cs new file mode 100644 index 00000000..27efb81a --- /dev/null +++ b/src/Microsoft.AspNet.FileSystems.Interfaces/IExpirationTrigger.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Framework.Expiration.Interfaces +{ +#if ASPNET50 || ASPNETCORE50 + [Framework.Runtime.AssemblyNeutral] +#endif + public interface IExpirationTrigger + { + /// + /// Checked each time the key is accessed in the cache. + /// + bool IsExpired { get; } + + /// + /// Indicates if this trigger will pro-actively trigger callbacks. Callbacks are still guaranteed to fire, eventually. + /// + bool ActiveExpirationCallbacks { get; } + + /// + /// Registers for a callback that will be invoked when the entries should be expired. + /// IsExpired MUST be set before the callback is invoked. + /// + /// + /// + /// + IDisposable RegisterExpirationCallback(Action callback, object state); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems/IFileInfo.cs b/src/Microsoft.AspNet.FileSystems.Interfaces/IFileInfo.cs similarity index 53% rename from src/Microsoft.AspNet.FileSystems/IFileInfo.cs rename to src/Microsoft.AspNet.FileSystems.Interfaces/IFileInfo.cs index 64afb430..87ecaf3c 100644 --- a/src/Microsoft.AspNet.FileSystems/IFileInfo.cs +++ b/src/Microsoft.AspNet.FileSystems.Interfaces/IFileInfo.cs @@ -3,26 +3,35 @@ using System; using System.IO; +using Microsoft.Framework.Expiration.Interfaces; namespace Microsoft.AspNet.FileSystems { /// /// Represents a file in the given file system. /// +#if ASPNET50 || ASPNETCORE50 + [Framework.Runtime.AssemblyNeutral] +#endif public interface IFileInfo { /// - /// The length of the file in bytes, or -1 for a directory info + /// True if resource exists in the underlying storage system. + /// + bool Exists { get; } + + /// + /// The length of the file in bytes, or -1 for a directory or non-existing files. /// long Length { get; } /// - /// The path to the file, including the file name. Return null if the file is not directly accessible. + /// The path to the file, including the file name. Return null if the file is not directly accessible. /// string PhysicalPath { get; } /// - /// The name of the file + /// The name of the file or directory, not including any path. /// string Name { get; } @@ -41,5 +50,26 @@ public interface IFileInfo /// /// The file stream Stream CreateReadStream(); + + /// + /// True if the file is readonly. + /// + bool IsReadOnly { get; } + + /// + /// Store new contents for resource. Folders will be created if needed. + /// + void WriteContent(byte[] content); + + /// + /// Deletes the file. + /// + void Delete(); + + /// + /// Gets a trigger to monitor the file changes. + /// + /// + IExpirationTrigger CreateFileChangeTrigger(); } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems.Interfaces/IFileSystem.cs b/src/Microsoft.AspNet.FileSystems.Interfaces/IFileSystem.cs new file mode 100644 index 00000000..1a3b6d45 --- /dev/null +++ b/src/Microsoft.AspNet.FileSystems.Interfaces/IFileSystem.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.FileSystems +{ + /// + /// A file system abstraction. + /// +#if ASPNET50 || ASPNETCORE50 + [Framework.Runtime.AssemblyNeutral] +#endif + public interface IFileSystem + { + /// + /// Locate a file at the given path. + /// + /// Relative path that identifies the file. + /// The file information. Caller must check Exists property. + IFileInfo GetFileInfo(string subpath); + + /// + /// Enumerate a directory at the given path, if any. + /// + /// Relative path that identifies the directory. + /// Returns the contents of the directory. + IDirectoryContents GetDirectoryContents(string subpath); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems.Interfaces/Microsoft.AspNet.FileSystems.Interfaces.kproj b/src/Microsoft.AspNet.FileSystems.Interfaces/Microsoft.AspNet.FileSystems.Interfaces.kproj new file mode 100644 index 00000000..7e0a92e1 --- /dev/null +++ b/src/Microsoft.AspNet.FileSystems.Interfaces/Microsoft.AspNet.FileSystems.Interfaces.kproj @@ -0,0 +1,26 @@ + + + + 12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + dd94b7e8-3a59-4f84-98a0-8139be259a87 + Library + Microsoft.AspNet.FileSystems.Interfaces + + + + ConsoleDebugger + + + WebDebugger + + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems.Interfaces/project.json b/src/Microsoft.AspNet.FileSystems.Interfaces/project.json new file mode 100644 index 00000000..74d99c19 --- /dev/null +++ b/src/Microsoft.AspNet.FileSystems.Interfaces/project.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0-*", + "description": "ASP.NET 5 File System interfaces.", + "dependencies": { + "Microsoft.Framework.Runtime.Interfaces": "1.0.0-*" + }, + "frameworks": { + "net45": { }, + "aspnet50": { }, + "aspnetcore50": { } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems/EmbeddedResourceFileSystem.cs b/src/Microsoft.AspNet.FileSystems/EmbeddedResourceFileSystem.cs index 8ce91e2a..c1d2dca1 100644 --- a/src/Microsoft.AspNet.FileSystems/EmbeddedResourceFileSystem.cs +++ b/src/Microsoft.AspNet.FileSystems/EmbeddedResourceFileSystem.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; +using Microsoft.Framework.Expiration.Interfaces; namespace Microsoft.AspNet.FileSystems { @@ -48,47 +49,56 @@ public EmbeddedResourceFileSystem(Assembly assembly, string baseNamespace) } /// - /// Locate a file at the given path + /// Locates a file at the given path. /// - /// The path that identifies the file - /// The discovered file if any - /// True if a file was located at the given path - public bool TryGetFileInfo(string subpath, out IFileInfo fileInfo) + /// The path that identifies the file. + /// The file information. Caller must check Exists property. + public IFileInfo GetFileInfo(string subpath) { - // "/file.txt" expected. - if (string.IsNullOrEmpty(subpath) || subpath[0] != '/') + if (string.IsNullOrEmpty(subpath)) { - fileInfo = null; - return false; + return new NotFoundFileInfo(subpath); } - string fileName = subpath.Substring(1); // Drop the leading '/' - string resourcePath = _baseNamespace + fileName; + // Relative paths starting with a leading slash okay + if (subpath.StartsWith("/", StringComparison.Ordinal)) + { + subpath = subpath.Substring(1); + } + + string resourcePath = _baseNamespace + subpath; + string name = Path.GetFileName(subpath); if (_assembly.GetManifestResourceInfo(resourcePath) == null) { - fileInfo = null; - return false; + return new NotFoundFileInfo(name); } - fileInfo = new EmbeddedResourceFileInfo(_assembly, resourcePath, fileName, _lastModified); - return true; + return new EmbeddedResourceFileInfo(_assembly, resourcePath, name, _lastModified); } /// - /// Enumerate a directory at the given path, if any. - /// This file system uses a flat directory structure. Everything under the base namespace is considered to be one directory. - /// - /// The path that identifies the directory - /// The contents if any - /// True if a directory was located at the given path - public bool TryGetDirectoryContents(string subpath, out IEnumerable contents) + /// Enumerate a directory at the given path, if any. + /// This file system uses a flat directory structure. Everything under the base namespace is considered to be one directory. + /// + /// The path that identifies the directory + /// Contents of the directory. Caller must check Exists property. + public IDirectoryContents GetDirectoryContents(string subpath) { // The file name is assumed to be the remainder of the resource name. + if (subpath == null) + { + return new NotFoundDirectoryContents(); + } + + // Relative paths starting with a leading slash okay + if (subpath.StartsWith("/", StringComparison.Ordinal)) + { + subpath = subpath.Substring(1); + } // Non-hierarchal. - if (!subpath.Equals("/")) + if (!subpath.Equals(string.Empty)) { - contents = null; - return false; + return new NotFoundDirectoryContents(); } IList entries = new List(); @@ -105,24 +115,7 @@ public bool TryGetDirectoryContents(string subpath, out IEnumerable c } } - contents = entries; - return true; - } - - /// - public bool TryGetParentPath(string subpath, out string parentPath) - { - if (string.IsNullOrEmpty(subpath) || - subpath[0] != '/' || - subpath.Equals("/", StringComparison.Ordinal)) - { - // If the path does not start with the root, or are already at the root, return false. - parentPath = null; - return false; - } - - parentPath = "/"; - return true; + return new EnumerableDirectoryContents(entries); } private class EmbeddedResourceFileInfo : IFileInfo @@ -130,16 +123,21 @@ private class EmbeddedResourceFileInfo : IFileInfo private readonly Assembly _assembly; private readonly DateTime _lastModified; private readonly string _resourcePath; - private readonly string _fileName; + private readonly string _name; private long? _length; - public EmbeddedResourceFileInfo(Assembly assembly, string resourcePath, string fileName, DateTime lastModified) + public EmbeddedResourceFileInfo(Assembly assembly, string resourcePath, string name, DateTime lastModified) { _assembly = assembly; _lastModified = lastModified; _resourcePath = resourcePath; - _fileName = fileName; + _name = name; + } + + public bool Exists + { + get { return true; } } public long Length @@ -165,7 +163,7 @@ public string PhysicalPath public string Name { - get { return _fileName; } + get { return _name; } } public DateTime LastModified @@ -178,6 +176,11 @@ public bool IsDirectory get { return false; } } + public bool IsReadOnly + { + get { return true; } + } + public Stream CreateReadStream() { Stream stream = _assembly.GetManifestResourceStream(_resourcePath); @@ -187,6 +190,21 @@ public Stream CreateReadStream() } return stream; } + + public void WriteContent(byte[] content) + { + throw new InvalidOperationException(string.Format("{0} does not support {1}.", nameof(EmbeddedResourceFileSystem), nameof(WriteContent))); + } + + public void Delete() + { + throw new InvalidOperationException(string.Format("{0} does not support {1}.", nameof(EmbeddedResourceFileSystem), nameof(Delete))); + } + + public IExpirationTrigger CreateFileChangeTrigger() + { + throw new NotSupportedException(string.Format("{0} does not support {1}.", nameof(EmbeddedResourceFileSystem), nameof(CreateFileChangeTrigger))); + } } } } diff --git a/src/Microsoft.AspNet.FileSystems/IFileSystem.cs b/src/Microsoft.AspNet.FileSystems/IFileSystem.cs deleted file mode 100644 index 8ea8715a..00000000 --- a/src/Microsoft.AspNet.FileSystems/IFileSystem.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.AspNet.FileSystems -{ - /// - /// A file system abstraction - /// - public interface IFileSystem - { - /// - /// Locate a file at the given path - /// - /// The path that identifies the file - /// The discovered file if any - /// True if a file was located at the given path - bool TryGetFileInfo(string subpath, out IFileInfo fileInfo); - - /// - /// Enumerate a directory at the given path, if any - /// - /// The path that identifies the directory - /// The contents if any - /// True if a directory was located at the given path - bool TryGetDirectoryContents(string subpath, out IEnumerable contents); - - /// - /// Gets the parent directory for the specified . - /// - /// A path under the application root that identifies either a file or a directory. - /// The path to the parent directory. - /// True if the parent directory is rooted inside this instance of , false otherwise. - bool TryGetParentPath(string subpath, out string parentPath); - } -} diff --git a/src/Microsoft.AspNet.FileSystems/Implementation/EnumerableDirectoryContents.cs b/src/Microsoft.AspNet.FileSystems/Implementation/EnumerableDirectoryContents.cs new file mode 100644 index 00000000..1e24e389 --- /dev/null +++ b/src/Microsoft.AspNet.FileSystems/Implementation/EnumerableDirectoryContents.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.AspNet.FileSystems +{ + public class EnumerableDirectoryContents : IDirectoryContents + { + private readonly IEnumerable _entries; + + public EnumerableDirectoryContents(IEnumerable entries) + { + _entries = entries; + } + + public bool Exists + { + get { return true; } + } + + public IEnumerator GetEnumerator() + { + return _entries.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _entries.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems/Implementation/NotFoundDirectoryContents.cs b/src/Microsoft.AspNet.FileSystems/Implementation/NotFoundDirectoryContents.cs new file mode 100644 index 00000000..3d83ef75 --- /dev/null +++ b/src/Microsoft.AspNet.FileSystems/Implementation/NotFoundDirectoryContents.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNet.FileSystems +{ + public class NotFoundDirectoryContents : IDirectoryContents + { + public NotFoundDirectoryContents() + { + } + + public bool Exists + { + get { return false; } + } + + public IEnumerator GetEnumerator() + { + return Enumerable.Empty().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems/Implementation/NotFoundFileInfo.cs b/src/Microsoft.AspNet.FileSystems/Implementation/NotFoundFileInfo.cs new file mode 100644 index 00000000..b1e71237 --- /dev/null +++ b/src/Microsoft.AspNet.FileSystems/Implementation/NotFoundFileInfo.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.Framework.Expiration.Interfaces; + +namespace Microsoft.AspNet.FileSystems +{ + public class NotFoundFileInfo : IFileInfo + { + private readonly string _name; + + public NotFoundFileInfo(string name) + { + _name = name; + } + + public bool Exists + { + get { return false; } + } + + public bool IsDirectory + { + get { return false; } + } + + public DateTime LastModified + { + get { return DateTime.MinValue; } + } + + public long Length + { + get { return -1; } + } + + public string Name + { + get { return _name; } + } + + public string PhysicalPath + { + get { return null; } + } + + public bool IsReadOnly + { + get + { + throw new InvalidOperationException(string.Format("{0} does not support {1}.", nameof(NotFoundFileInfo), nameof(IsReadOnly))); + } + } + + public Stream CreateReadStream() + { + throw new InvalidOperationException(string.Format("{0} does not support {1}.", nameof(NotFoundFileInfo), nameof(CreateReadStream))); + } + + public void WriteContent(byte[] content) + { + throw new InvalidOperationException(string.Format("{0} does not support {1}.", nameof(NotFoundFileInfo), nameof(WriteContent))); + } + + public void Delete() + { + throw new InvalidOperationException(string.Format("{0} does not support {1}.", nameof(NotFoundFileInfo), nameof(Delete))); + } + + public IExpirationTrigger CreateFileChangeTrigger() + { + throw new InvalidOperationException(string.Format("{0} does not support {1}.", nameof(NotFoundFileInfo), nameof(CreateFileChangeTrigger))); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems/PhysicalFileSystem.cs b/src/Microsoft.AspNet.FileSystems/PhysicalFileSystem.cs index 9c09b708..911483de 100644 --- a/src/Microsoft.AspNet.FileSystems/PhysicalFileSystem.cs +++ b/src/Microsoft.AspNet.FileSystems/PhysicalFileSystem.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using Microsoft.Framework.Expiration.Interfaces; namespace Microsoft.AspNet.FileSystems { @@ -94,60 +95,74 @@ private static string EnsureTrailingSlash(string path) /// Locate a file at the given path by directly mapping path segments to physical directories. /// /// A path under the root directory - /// The discovered file, if any - /// True if a file was discovered at the given path - public bool TryGetFileInfo(string subpath, out IFileInfo fileInfo) + /// The file information. Caller must check Exists property. + public IFileInfo GetFileInfo(string subpath) { - try + if (string.IsNullOrEmpty(subpath)) { - // For *nix systems, absolute paths would start with '/'. Trimming the slash in - // such scenarios would result in incorrect interpretation of path. We'll do a check - // to verify that the path doesn't already look like it's rooted before trimming the - // '/' token indicating rooted relative to this FileSystem's Root. - if (!IsRooted(subpath) && subpath.StartsWith("/", StringComparison.Ordinal)) - { - subpath = subpath.Substring(1); - } - var fullPath = GetFullPath(subpath); - if (fullPath != null) - { - var info = new FileInfo(fullPath); - if (info.Exists && !IsRestricted(info)) - { - fileInfo = new PhysicalFileInfo(info); - return true; - } - } + return new NotFoundFileInfo(subpath); } - catch (ArgumentException) + + // Relative paths starting with a leading slash okay + if (subpath.StartsWith("/", StringComparison.Ordinal)) { + subpath = subpath.Substring(1); } - fileInfo = null; - return false; + + // Absolute paths not permitted. + if (Path.IsPathRooted(subpath)) + { + return new NotFoundFileInfo(subpath); + } + + var fullPath = GetFullPath(subpath); + if (fullPath == null || IsRestricted(subpath)) + { + return new NotFoundFileInfo(subpath); + } + + var fileInfo = new FileInfo(fullPath); + if (fileInfo.Exists) + { + return new PhysicalFileInfo(fileInfo); + } + + return new NotFoundFileInfo(subpath); } /// /// Enumerate a directory at the given path, if any. /// /// A path under the root directory - /// The discovered directories, if any - /// True if a directory was discovered at the given path - public bool TryGetDirectoryContents(string subpath, out IEnumerable contents) + /// Contents of the directory. Caller must check Exists property. + public IDirectoryContents GetDirectoryContents(string subpath) { try { - if (!IsRooted(subpath) && subpath.StartsWith("/", StringComparison.Ordinal)) + if (subpath == null) + { + return new NotFoundDirectoryContents(); + } + + // Relative paths starting with a leading slash okay + if (subpath.StartsWith("/", StringComparison.Ordinal)) { subpath = subpath.Substring(1); } + + // Absolute paths not permitted. + if (Path.IsPathRooted(subpath)) + { + return new NotFoundDirectoryContents(); + } + var fullPath = GetFullPath(subpath); if (fullPath != null) { var directoryInfo = new DirectoryInfo(fullPath); if (!directoryInfo.Exists) { - contents = null; - return false; + return new NotFoundDirectoryContents(); } IEnumerable physicalInfos = directoryInfo.EnumerateFileSystemInfos(); @@ -164,73 +179,22 @@ public bool TryGetDirectoryContents(string subpath, out IEnumerable c virtualInfos.Add(new PhysicalDirectoryInfo((DirectoryInfo)fileSystemInfo)); } } - contents = virtualInfos; - return true; + + return new EnumerableDirectoryContents(virtualInfos); } } - catch (ArgumentException) - { - } catch (DirectoryNotFoundException) { } catch (IOException) { } - contents = null; - return false; + return new NotFoundDirectoryContents(); } - /// - public bool TryGetParentPath(string subpath, out string parentPath) + private bool IsRestricted(string name) { - if (string.IsNullOrEmpty(subpath)) - { - parentPath = null; - return false; - } - - if (!IsRooted(subpath) && subpath[0] == '/') - { - subpath = subpath.Substring(1); - } - - var fullPath = GetFullPath(subpath); - if (fullPath != null) - { - DirectoryInfo parentDirectory = null; - if (Directory.Exists(fullPath)) - { - parentDirectory = new DirectoryInfo(fullPath).Parent; - } - else if (File.Exists(fullPath)) - { - parentDirectory = new FileInfo(fullPath).Directory; - } - - if (parentDirectory != null) - { - // If the parent directory is set, verify it's is rooted under this FileSystem's root. - // The appRoot itself needs to be special cased since we add a trailing slash when - // generating it. - var pathWithTrailingSlash = EnsureTrailingSlash(parentDirectory.FullName); - if (IsRooted(pathWithTrailingSlash)) - { - parentPath = Root.Length == pathWithTrailingSlash.Length ? - string.Empty : - pathWithTrailingSlash.Substring(Root.Length, pathWithTrailingSlash.Length - Root.Length - 1); - return true; - } - } - } - - parentPath = null; - return false; - } - - private bool IsRestricted(FileInfo fileInfo) - { - string fileName = Path.GetFileNameWithoutExtension(fileInfo.Name); + string fileName = Path.GetFileNameWithoutExtension(name); return RestrictedFileNames.ContainsKey(fileName); } @@ -243,6 +207,11 @@ public PhysicalFileInfo(FileInfo info) _info = info; } + public bool Exists + { + get { return _info.Exists; } + } + public long Length { get { return _info.Length; } @@ -268,16 +237,41 @@ public bool IsDirectory get { return false; } } + public bool IsReadOnly + { + get + { +#if ASPNET50 + return _info.IsReadOnly; +#else + // TODO: Why property not available on CoreCLR + return false; +#endif + } + } + public Stream CreateReadStream() { -#if NET45 // Note: Buffer size must be greater than zero, even if the file size is zero. return new FileStream(PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024 * 64, FileOptions.Asynchronous | FileOptions.SequentialScan); -#else - // Note: Buffer size must be greater than zero, even if the file size is zero. - return new FileStream(PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024 * 64); -#endif + } + + public void WriteContent(byte[] content) + { + File.WriteAllBytes(PhysicalPath, content); + _info.Refresh(); + } + + public void Delete() + { + File.Delete(PhysicalPath); + _info.Refresh(); + } + + public IExpirationTrigger CreateFileChangeTrigger() + { + throw new NotImplementedException(); } } @@ -290,6 +284,11 @@ public PhysicalDirectoryInfo(DirectoryInfo info) _info = info; } + public bool Exists + { + get { return _info.Exists; } + } + public long Length { get { return -1; } @@ -315,10 +314,30 @@ public bool IsDirectory get { return true; } } + public bool IsReadOnly + { + get { return true; } + } + public Stream CreateReadStream() { - return null; + throw new InvalidOperationException(string.Format("{0} does not support {1}.", nameof(PhysicalDirectoryInfo), nameof(CreateReadStream))); + } + + public void WriteContent(byte[] content) + { + throw new InvalidOperationException(string.Format("{0} does not support {1}.", nameof(PhysicalDirectoryInfo), nameof(WriteContent))); + } + + public void Delete() + { + Directory.Delete(PhysicalPath, recursive: true); + } + + public IExpirationTrigger CreateFileChangeTrigger() + { + throw new NotSupportedException(string.Format("{0} does not support {1}.", nameof(PhysicalDirectoryInfo), nameof(CreateFileChangeTrigger))); } } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.FileSystems/project.json b/src/Microsoft.AspNet.FileSystems/project.json index d2b74374..32707138 100644 --- a/src/Microsoft.AspNet.FileSystems/project.json +++ b/src/Microsoft.AspNet.FileSystems/project.json @@ -1,20 +1,17 @@ { - "version": "1.0.0-*", - "description": "ASP.NET 5 File System abstractions.", - "frameworks": { - "net45": { }, - "aspnet50": { }, - "aspnetcore50": { - "dependencies": { - "System.Collections": "4.0.10-beta-*", - "System.IO": "4.0.10-beta-*", - "System.IO.FileSystem": "4.0.0-beta-*", - "System.Reflection": "4.0.10-beta-*", - "System.Reflection.Extensions": "4.0.0-beta-*", - "System.Runtime": "4.0.20-beta-*", - "System.Runtime.Extensions": "4.0.10-beta-*", - "System.Runtime.InteropServices": "4.0.20-beta-*" - } + "version": "1.0.0-*", + "description": "Implementation for ASP.NET 5 File System abstractions.", + "dependencies": { + "Microsoft.AspNet.FileSystems.Interfaces": "1.0.0-*" + }, + "frameworks": { + "net45": { }, + "aspnet50": { }, + "aspnetcore50": { + "dependencies": { + "System.IO.FileSystem": "4.0.0-beta-*", + "System.Linq": "4.0.0-beta-*" + } + } } - } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.FileSystems.Tests/EmbeddedResourceFileSystemTests.cs b/test/Microsoft.AspNet.FileSystems.Tests/EmbeddedResourceFileSystemTests.cs index 62d03046..2d2707f1 100644 --- a/test/Microsoft.AspNet.FileSystems.Tests/EmbeddedResourceFileSystemTests.cs +++ b/test/Microsoft.AspNet.FileSystems.Tests/EmbeddedResourceFileSystemTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; +using System.IO; using System.Linq; using Shouldly; using Xunit; @@ -12,138 +12,93 @@ namespace Microsoft.AspNet.FileSystems public class EmbeddedResourceFileSystemTests { [Fact] - public void When_TryGetFileInfo_and_resource_does_not_exist_then_should_not_get_file_info() + public void When_GetFileInfo_and_resource_does_not_exist_then_should_not_get_file_info() { var provider = new EmbeddedResourceFileSystem(this.GetType().Assembly, ""); - IFileInfo fileInfo; - provider.TryGetFileInfo("/DoesNotExist.Txt", out fileInfo).ShouldBe(false); - - fileInfo.ShouldBe(null); + var fileInfo = provider.GetFileInfo("DoesNotExist.Txt"); + fileInfo.ShouldNotBe(null); + fileInfo.Exists.ShouldBe(false); } [Fact] - public void When_TryGetFileInfo_and_resource_exists_in_root_then_should_get_file_info() + public void When_GetFileInfo_and_resource_exists_in_root_then_should_get_file_info() { var provider = new EmbeddedResourceFileSystem(this.GetType().Assembly, ""); + var expectedFileLength = new FileInfo("File.txt").Length; + var fileInfo = provider.GetFileInfo("File.txt"); + fileInfo.ShouldNotBe(null); + fileInfo.Exists.ShouldBe(true); + fileInfo.LastModified.ShouldNotBe(default(DateTime)); + fileInfo.Length.ShouldBe(expectedFileLength); + fileInfo.IsDirectory.ShouldBe(false); + fileInfo.PhysicalPath.ShouldBe(null); + fileInfo.Name.ShouldBe("File.txt"); - IFileInfo fileInfo; - provider.TryGetFileInfo("/File.txt", out fileInfo).ShouldBe(true); - + //Passing in a leading slash + fileInfo = provider.GetFileInfo("/File.txt"); fileInfo.ShouldNotBe(null); + fileInfo.Exists.ShouldBe(true); fileInfo.LastModified.ShouldNotBe(default(DateTime)); - fileInfo.Length.ShouldBeGreaterThan(0); + fileInfo.Length.ShouldBe(expectedFileLength); fileInfo.IsDirectory.ShouldBe(false); fileInfo.PhysicalPath.ShouldBe(null); fileInfo.Name.ShouldBe("File.txt"); } [Fact] - public void When_TryGetFileInfo_and_resource_exists_in_subdirectory_then_should_get_file_info() + public void When_GetFileInfo_and_resource_exists_in_subdirectory_then_should_get_file_info() { var provider = new EmbeddedResourceFileSystem(this.GetType().Assembly, "Resources"); - IFileInfo fileInfo; - provider.TryGetFileInfo("/ResourcesInSubdirectory/File3.txt", out fileInfo).ShouldBe(true); - + var fileInfo = provider.GetFileInfo("ResourcesInSubdirectory/File3.txt"); fileInfo.ShouldNotBe(null); + fileInfo.Exists.ShouldBe(true); fileInfo.LastModified.ShouldNotBe(default(DateTime)); fileInfo.Length.ShouldBeGreaterThan(0); fileInfo.IsDirectory.ShouldBe(false); fileInfo.PhysicalPath.ShouldBe(null); - fileInfo.Name.ShouldBe("ResourcesInSubdirectory/File3.txt"); + fileInfo.Name.ShouldBe("File3.txt"); } [Fact] - public void When_TryGetFileInfo_and_resources_in_path_then_should_get_file_infos() + public void When_GetFileInfo_and_resources_in_path_then_should_get_file_infos() { var provider = new EmbeddedResourceFileSystem(this.GetType().Assembly, ""); - IFileInfo fileInfo; - provider.TryGetFileInfo("/Resources/File.txt", out fileInfo).ShouldBe(true); - + var fileInfo = provider.GetFileInfo("Resources/File.txt"); fileInfo.ShouldNotBe(null); + fileInfo.Exists.ShouldBe(true); fileInfo.LastModified.ShouldNotBe(default(DateTime)); fileInfo.Length.ShouldBeGreaterThan(0); fileInfo.IsDirectory.ShouldBe(false); fileInfo.PhysicalPath.ShouldBe(null); - fileInfo.Name.ShouldBe("Resources/File.txt"); + fileInfo.Name.ShouldBe("File.txt"); } [Fact] - public void TryGetDirInfo_with_slash() + public void GetDirectoryContents() { var provider = new EmbeddedResourceFileSystem(this.GetType().Assembly, "Resources"); - IEnumerable files; - provider.TryGetDirectoryContents("/", out files).ShouldBe(true); + var files = provider.GetDirectoryContents(""); + files.ShouldNotBe(null); files.Count().ShouldBe(2); - - provider.TryGetDirectoryContents("/file", out files).ShouldBe(false); - provider.TryGetDirectoryContents("/file/", out files).ShouldBe(false); - provider.TryGetDirectoryContents("/file.txt", out files).ShouldBe(false); - provider.TryGetDirectoryContents("/file/txt", out files).ShouldBe(false); - } - - [Fact] - public void TryGetDirInfo_without_slash() - { - var provider = new EmbeddedResourceFileSystem(this.GetType().Assembly, ""); - - IEnumerable files; - provider.TryGetDirectoryContents(string.Empty, out files).ShouldBe(false); - provider.TryGetDirectoryContents("file", out files).ShouldBe(false); - provider.TryGetDirectoryContents("file.txt", out files).ShouldBe(false); + provider.GetDirectoryContents("file").Exists.ShouldBe(false); + provider.GetDirectoryContents("file/").Exists.ShouldBe(false); + provider.GetDirectoryContents("file.txt").Exists.ShouldBe(false); + provider.GetDirectoryContents("file/txt").Exists.ShouldBe(false); } [Fact] - public void TryGetDirInfo_with_no_matching_base_namespace() + public void GetDirInfo_with_no_matching_base_namespace() { var provider = new EmbeddedResourceFileSystem(this.GetType().Assembly, "Unknown.Namespace"); - IEnumerable files; - provider.TryGetDirectoryContents(string.Empty, out files).ShouldBe(false); - provider.TryGetDirectoryContents("/", out files).ShouldBe(true); + var files = provider.GetDirectoryContents(string.Empty); + files.ShouldNotBe(null); + files.Exists.ShouldBe(true); files.Count().ShouldBe(0); } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData("path-without-slash")] - public void TryGetParentPath_ReturnsFalseIfPathDoesNotStartWithSlash(string subpath) - { - // Arrange - var provider = new EmbeddedResourceFileSystem(GetType().Assembly, "BaseNamespace"); - - // Act and Assert - string parentPath; - provider.TryGetParentPath(subpath, out parentPath).ShouldBe(false); - } - - [Fact] - public void TryGetParentPath_ReturnsFalseForPathThatIsSlash() - { - // Arrange - var provider = new EmbeddedResourceFileSystem(GetType().Assembly, "BaseNamespace"); - - // Act and Assert - string parentPath; - provider.TryGetParentPath("/", out parentPath).ShouldBe(false); - } - - [Theory] - [InlineData("/foo")] - [InlineData("/bar.resx")] - public void TryGetParentPath_ReturnsSlashForAllPathsThatStartWithSlash(string subpath) - { - // Arrange - var provider = new EmbeddedResourceFileSystem(GetType().Assembly, "BaseNamespace"); - - // Act and Assert - string parentPath; - provider.TryGetParentPath(subpath, out parentPath).ShouldBe(true); - parentPath.ShouldBe("/"); - } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.FileSystems.Tests/PhysicalFileSystemTests.cs b/test/Microsoft.AspNet.FileSystems.Tests/PhysicalFileSystemTests.cs index 9aff26f5..03f00dfd 100644 --- a/test/Microsoft.AspNet.FileSystems.Tests/PhysicalFileSystemTests.cs +++ b/test/Microsoft.AspNet.FileSystems.Tests/PhysicalFileSystemTests.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using Microsoft.Framework.Runtime; using Microsoft.Framework.Runtime.Infrastructure; using Shouldly; @@ -18,147 +18,136 @@ public class PhysicalFileSystemTests public void ExistingFilesReturnTrue() { var provider = new PhysicalFileSystem(Environment.CurrentDirectory); - IFileInfo info; - provider.TryGetFileInfo("File.txt", out info).ShouldBe(true); + var info = provider.GetFileInfo("File.txt"); info.ShouldNotBe(null); + info.Exists.ShouldBe(true); + info.IsReadOnly.ShouldBe(false); + + info = provider.GetFileInfo("/File.txt"); + info.ShouldNotBe(null); + info.Exists.ShouldBe(true); + info.IsReadOnly.ShouldBe(false); + } + + [Fact] + public void ModifyContent_And_Delete_File_Succeeds() + { + var fileName = Guid.NewGuid().ToString(); + var fileLocation = Path.Combine(Path.GetTempPath(), fileName); + File.WriteAllText(fileLocation, "OldContent"); + var provider = new PhysicalFileSystem(Path.GetTempPath()); + var fileInfo = provider.GetFileInfo(fileName); + fileInfo.Length.ShouldBe(new FileInfo(fileInfo.PhysicalPath).Length); + fileInfo.Exists.ShouldBe(true); + + // Write new content. + var newData = Encoding.UTF8.GetBytes("OldContent + NewContent"); + fileInfo.WriteContent(newData); + fileInfo.Exists.ShouldBe(true); + fileInfo.Length.ShouldBe(newData.Length); + + // Delete the file and verify file info is updated. + fileInfo.Delete(); + fileInfo.Exists.ShouldBe(false); + new FileInfo(fileLocation).Exists.ShouldBe(false); } [Fact] public void MissingFilesReturnFalse() { var provider = new PhysicalFileSystem(Environment.CurrentDirectory); - IFileInfo info; - provider.TryGetFileInfo("File5.txt", out info).ShouldBe(false); - info.ShouldBe(null); + var info = provider.GetFileInfo("File5.txt"); + info.ShouldNotBe(null); + info.Exists.ShouldBe(false); } [Fact] public void SubPathActsAsRoot() { var provider = new PhysicalFileSystem(Path.Combine(Environment.CurrentDirectory, "sub")); - IFileInfo info; - provider.TryGetFileInfo("File2.txt", out info).ShouldBe(true); + var info = provider.GetFileInfo("File2.txt"); info.ShouldNotBe(null); + info.Exists.ShouldBe(true); } [Fact] - public void RelativeOrAbsolutePastRootNotAllowed() + public void GetDirectoryContents_FromRootPath_ForEmptyDirectoryName() { - var serviceProvider = CallContextServiceLocator.Locator.ServiceProvider; - var appEnvironment = (IApplicationEnvironment)serviceProvider.GetService(typeof(IApplicationEnvironment)); - var provider = new PhysicalFileSystem(Path.Combine(Environment.CurrentDirectory, "sub")); - IFileInfo info; - - provider.TryGetFileInfo("..\\File.txt", out info).ShouldBe(false); - info.ShouldBe(null); - - provider.TryGetFileInfo(".\\..\\File.txt", out info).ShouldBe(false); - info.ShouldBe(null); - - var applicationBase = appEnvironment.ApplicationBasePath; - var file1 = Path.Combine(applicationBase, "File.txt"); - var file2 = Path.Combine(applicationBase, "sub", "File2.txt"); - provider.TryGetFileInfo(file1, out info).ShouldBe(false); - info.ShouldBe(null); - - provider.TryGetFileInfo(file2, out info).ShouldBe(true); + var info = provider.GetDirectoryContents(string.Empty); info.ShouldNotBe(null); - info.PhysicalPath.ShouldBe(file2); + info.Exists.ShouldBe(true); + var firstDirectory = info.Where(f => f.IsDirectory).Where(f => f.Exists).FirstOrDefault(); + Should.Throw(() => firstDirectory.CreateReadStream()); + Should.Throw(() => firstDirectory.WriteContent(new byte[10])); + Should.Throw(() => firstDirectory.CreateFileChangeTrigger()); + + var fileInfo = info.Where(f => f.Name == "File2.txt").FirstOrDefault(); + fileInfo.ShouldNotBe(null); + fileInfo.Exists.ShouldBe(true); + } - provider.TryGetFileInfo("/File2.txt", out info).ShouldBe(true); - info.ShouldNotBe(null); - info.PhysicalPath.ShouldBe(file2); + [Fact] + public void NotFoundFileInfo_BasicTests() + { + var info = new NotFoundFileInfo("NotFoundFile.txt"); + Should.Throw(() => info.CreateReadStream()); + Should.Throw(() => info.WriteContent(new byte[10])); + Should.Throw(() => info.Delete()); + Should.Throw(() => info.CreateFileChangeTrigger()); } - [Theory] - [InlineData(null)] - [InlineData("")] - public void TryGetParentPath_ReturnsFalseIfPathIsNullOrEmpty(string subpath) + [Fact] + public void RelativePathPastRootNotAllowed() { - // Arrange + var serviceProvider = CallContextServiceLocator.Locator.ServiceProvider; + var appEnvironment = (IApplicationEnvironment)serviceProvider.GetService(typeof(IApplicationEnvironment)); + var provider = new PhysicalFileSystem(Path.Combine(Environment.CurrentDirectory, "sub")); - // Act and Assert - string parentPath; - provider.TryGetParentPath(subpath, out parentPath).ShouldBe(false); - } + var info = provider.GetFileInfo("..\\File.txt"); + info.ShouldNotBe(null); + info.Exists.ShouldBe(false); + info = provider.GetFileInfo(".\\..\\File.txt"); + info.ShouldNotBe(null); + info.Exists.ShouldBe(false); - public static IEnumerable TryGetParentPath_ReturnsFalseIfPathIsNotSubDirectoryOfRootData - { - get - { - yield return new[] { Directory.GetCurrentDirectory() }; - yield return new[] { @"x:\fake\test" }; - } + info = provider.GetFileInfo("File2.txt"); + info.ShouldNotBe(null); + info.Exists.ShouldBe(true); + info.PhysicalPath.ShouldBe(Path.Combine(appEnvironment.ApplicationBasePath, "sub", "File2.txt")); } - [Theory] - [MemberData("TryGetParentPath_ReturnsFalseIfPathIsNotSubDirectoryOfRootData")] - public void TryGetParentPath_ReturnsFalseIfPathIsNotSubDirectoryOfRoot(string subpath) + [Fact] + public void AbsolutePathNotAllowed() { - // Arrange + var serviceProvider = CallContextServiceLocator.Locator.ServiceProvider; + var appEnvironment = (IApplicationEnvironment)serviceProvider.GetService(typeof(IApplicationEnvironment)); + var provider = new PhysicalFileSystem(Path.Combine(Environment.CurrentDirectory, "sub")); - // Act and Assert - string parentPath; - provider.TryGetParentPath(subpath, out parentPath).ShouldBe(false); - } + var applicationBase = appEnvironment.ApplicationBasePath; + var file1 = Path.Combine(applicationBase, "File.txt"); + + var info = provider.GetFileInfo(file1); + info.ShouldNotBe(null); + info.Exists.ShouldBe(false); - [Theory] - [InlineData("", "sub", "")] - [InlineData("", "/sub", "")] - [InlineData("", @"sub/File2.txt", @"sub")] - [InlineData("", @"/sub/dir/File3.txt", @"sub/dir")] - [InlineData("sub", @"File2.txt", "")] - public void TryGetParentPath_ReturnsParentPath(string root, string subpath, string expected) - { - // Arrange - var provider = new PhysicalFileSystem(Path.Combine(Environment.CurrentDirectory, root)); - - // Act and Assert - string parentPath; - provider.TryGetParentPath(subpath, out parentPath).ShouldBe(true); - // Convert backslash paths to forward slash so we can test with the same test data on Windows and *nix. - expected.ShouldBe(parentPath.Replace('\\', '/')); - } + var file2 = Path.Combine(applicationBase, "sub", "File2.txt"); + info = provider.GetFileInfo(file2); + info.ShouldNotBe(null); + info.Exists.ShouldBe(false); - [Theory] - [InlineData("sub/File2.txt")] - [InlineData(@"/sub/File2.txt")] - [InlineData(@"sub/dir")] - public void TryGetParentPath_AllowsTraversingToTheRoot(string input) - { - // Arrange - var provider = new PhysicalFileSystem(Environment.CurrentDirectory); + var directory1 = Path.Combine(applicationBase, "sub"); + var directoryContents = provider.GetDirectoryContents(directory1); + info.ShouldNotBe(null); + info.Exists.ShouldBe(false); - // Act and Assert - 1 - string path1; - provider.TryGetParentPath(input, out path1).ShouldBe(true); - path1.ShouldBe(@"sub"); - - // Act and Assert - 2 - IEnumerable contents; - provider.TryGetDirectoryContents(path1, out contents).ShouldBe(true); - contents.Count().ShouldBe(2); - contents = contents.OrderBy(f => f.Name); - contents.First().Name.ShouldBe("dir"); - string subPathParent; - provider.TryGetParentPath(Path.Combine(path1, "dir"), out subPathParent).ShouldBe(true); - subPathParent.ShouldBe(path1); - contents.Last().Name.ShouldBe("File2.txt"); - provider.TryGetParentPath(Path.Combine(path1, "dir"), out subPathParent).ShouldBe(true); - subPathParent.ShouldBe(path1); - - // Act and Assert - 3 - string path2; - provider.TryGetParentPath(path1, out path2).ShouldBe(true); - path2.ShouldBe(""); - - // Act and Assert - 4 - string path3; - provider.TryGetParentPath(path2, out path3).ShouldBe(false); + var directory2 = Path.Combine(applicationBase, "Does_Not_Exists"); + directoryContents = provider.GetDirectoryContents(directory2); + info.ShouldNotBe(null); + info.Exists.ShouldBe(false); } } -} +} \ No newline at end of file