Prismo based fixes and improvements

This commit is contained in:
grzegorz.russek
2024-05-15 09:46:01 +02:00
parent 5f748e55a2
commit 1b5f288439
21 changed files with 2191 additions and 151 deletions

View File

@@ -49,7 +49,7 @@ namespace VirtualFS.Base
public BaseFileSystem(bool readOnly)
{
IsReadOnly = readOnly;
HighPriority = false;
Priority = FileSystemMountPriority.Normal;
}
/// <summary>Gets reference to root file system.</summary>
@@ -57,8 +57,8 @@ namespace VirtualFS.Base
/// <summary>Gets or sets a value indicating whether this file system
/// has higher priority than others mounted on the same path.</summary>
/// <remarks>This value must be set corectly before mount.</remarks>
public virtual bool HighPriority { get; set; }
/// <remarks>This value must be set correctly before mount.</remarks>
public virtual FileSystemMountPriority Priority { get; set; }
/// <summary>
/// Gets a value indicating whether this file system is read only.
@@ -82,25 +82,42 @@ namespace VirtualFS.Base
}
}
/// <summary>
/// Check if given path exists.
/// </summary>
/// <summary>Gets or sets name of file system.</summary>
public virtual string Name { get; set; }
/// <summary>Check if given path exists.</summary>
/// <param name="path">Path to check.</param>
/// <returns>
/// Returns <c>true</c> if entry does
/// exist, otherwise <c>false</c>.
/// </returns>
/// <returns>Returns <c>true</c> if entry does
/// exist, otherwise <c>false</c>.</returns>
public abstract bool Exists(Path path);
/// <summary>
/// Get entry located under given path.
/// </summary>
/// <summary>Get entry located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <returns>
/// Returns entry information.
/// </returns>
/// <returns>Returns entry information.</returns>
public abstract Entry GetEntry(Path path);
/// <summary>Get directory located under given path.</summary>
/// <param name="path">The path.</param>
/// <returns>Returns directory entry information.</returns>
public Directory GetDirectory(Path path)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't get directory from file path '{0}'", path));
return GetEntry(path) as Directory;
}
/// <summary>Get file located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <returns>Returns file entry information.</returns>
public File GetFile(Path path)
{
if (path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't get file from directory path '{0}'", path));
return GetEntry(path) as File;
}
/// <summary>Get path to physical file containing entry.</summary>
/// <param name="path">Virtual file system path.</param>
/// <returns>Real file system path or <c>null</c> if real
@@ -156,7 +173,7 @@ namespace VirtualFS.Base
if (source == destination)
return;
if (!destination.IsDirectory)
if (source.IsDirectory && !destination.IsDirectory)
throw new InvalidOperationException(string.Format("Can't copy to non directory path ('{0}').", destination));
if (!source.IsDirectory)
@@ -209,7 +226,7 @@ namespace VirtualFS.Base
private void InternalCopySingleFile(Path source, Path destination, bool overwrite, Action<Path, Path> copySuccess)
{
var dst = destination.Append(source.Name);
var dst = destination.IsDirectory ? destination.Append(source.Name) : destination;
if (!Exists(source))
throw new InvalidOperationException("Source file doesn't exist.");
@@ -217,17 +234,17 @@ namespace VirtualFS.Base
if (!overwrite && Exists(dst))
throw new InvalidOperationException("Destination file already exist.");
Directory dir = (Directory)GetEntry(destination);
Directory dir = (Directory)GetEntry(dst.Parent);
if (dir.IsReadOnly || dir.FileSystem.IsReadOnly)
throw new InvalidOperationException("Can't copy on read only file system.");
dir
.FileCreate(source.Name)
.FileCreate(dst.Name)
.WriteAllBytes(((File)GetEntry(source)).ReadAllBytes());
if (copySuccess != null)
copySuccess(source, destination.Append(source.Name));
copySuccess(source, dst);
}
/// <summary>Change name of the entry under specified path.</summary>
@@ -255,6 +272,36 @@ namespace VirtualFS.Base
/// </returns>
public abstract IEnumerable<Entry> GetEntries(Path path, Regex mask = null);
/// <summary>
/// Get directories located under given path.
/// </summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>
/// Returns entry information.
/// </returns>
public virtual IEnumerable<Directory> GetDirectories(Path path, Regex mask = null)
{
return GetEntries(path, mask)
.Where(e => e is Directory)
.Cast<Directory>();
}
/// <summary>
/// Get files located under given path.
/// </summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>
/// Returns entry information.
/// </returns>
public virtual IEnumerable<File> GetFiles(Path path, Regex mask = null)
{
return GetEntries(path, mask)
.Where(e => e is File)
.Cast<File>();
}
/// <summary>
/// Create Directory and return new entry.
/// </summary>
@@ -305,43 +352,11 @@ namespace VirtualFS.Base
throw new InvalidOperationException("Can't delete directory that contains entries.");
}
/// <summary>Opens an existing file for reading.</summary>
/// <param name="path">The file to be opened for reading.</param>
/// <returns>A read-only <see cref="Stream"/> on the specified path.</returns>
public virtual Stream FileOpenRead(Path path)
{
if (path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't open directory path ('{0}') as file.", path));
if (!Exists(path))
throw new InvalidOperationException("Can't open not existing file.");
return null;
}
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <returns>An unshared <see cref="Stream"/> object on
/// the specified path with write access.</returns>
public virtual Stream FileOpenWrite(Path path)
{
if (path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't open directory path ('{0}') as file.", path));
if (IsReadOnly)
throw new InvalidOperationException(string.Format("Can't write file ('{0}') on read only file system.", path));
if (!Exists(path))
throw new InvalidOperationException("Can't open not existing file.");
return null;
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <summary>Creates empty file on file system and returns it's description.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <returns>A <see cref="Stream"/> that provides
/// write access to the file specified in path.</returns>
public virtual Stream FileCreate(Path path)
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>File description.</returns>
public virtual File FileTouch(Path path, bool overwrite = false)
{
if (path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't open directory path ('{0}') as file.", path));
@@ -352,6 +367,74 @@ namespace VirtualFS.Base
if (!Exists(path.Parent))
throw new InvalidOperationException("Can't create file in un existent directory path.");
if (Exists(path))
{
if (overwrite)
FileDelete(path);
else
throw new InvalidOperationException("Can't create file that already exist.");
}
return null;
}
/// <summary>Opens an existing file for reading.</summary>
/// <param name="path">The file to be opened for reading.</param>
/// <returns>A read-only <see cref="Stream"/> on the specified path.</returns>
public virtual Stream FileOpenRead(Path path)
{
if (path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't open directory path ('{0}') as file.", path));
if (!Exists(path))
throw new InvalidOperationException(String.Format("Can't open not existing file '{0}'. ", path));
return null;
}
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <param name="append">Append file.</param>
/// <returns>An unshared <see cref="Stream"/> object on
/// the specified path with write access.</returns>
public virtual Stream FileOpenWrite(Path path, bool append = false)
{
if (path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't open directory path ('{0}') as file.", path));
if (IsReadOnly)
throw new InvalidOperationException(string.Format("Can't write file ('{0}') on read only file system.", path));
if (!Exists(path))
throw new InvalidOperationException(String.Format("Can't open not existing file '{0}'. ", path));
return null;
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream"/> that provides
/// write access to the file specified in path.</returns>
public virtual Stream FileCreate(Path path, bool overwrite = false)
{
if (path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't open directory path ('{0}') as file.", path));
if (IsReadOnly)
throw new InvalidOperationException(string.Format("Can't create file ('{0}') on read only file system.", path));
if (!Exists(path.Parent))
throw new InvalidOperationException("Can't create file in un existent directory path.");
if (Exists(path))
{
if (overwrite)
FileDelete(path);
else
throw new InvalidOperationException("Can't create file that already exist.");
}
return null;
}

View File

@@ -43,7 +43,7 @@ namespace VirtualFS.Compressed
/// <summary>Gets the Zip file.</summary>
public ZipFile File { get; private set; }
private Dictionary<Path, Entry> _pathToEntry;
private Dictionary<Path, Entry> _pathToEntry = new Dictionary<Path, Entry>();
/// <summary>Gets the assembly on which file system operates.</summary>
public Assembly Assembly { get; private set; }
@@ -55,6 +55,12 @@ namespace VirtualFS.Compressed
public ZipReadFileSystem(System.IO.Stream stream, Path root = null, Regex filter = null)
: base(true)
{
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanSeek)
throw new InvalidOperationException("Zip steram must be capable of seeking.");
File = new ZipFile(stream);
foreach (ZipEntry zipEntry in File)
@@ -91,6 +97,7 @@ namespace VirtualFS.Compressed
CreationTime = zipEntry.DateTime,
LastAccessTime = zipEntry.DateTime,
LastWriteTime = zipEntry.DateTime,
Size = zipEntry.Size,
});
}
}
@@ -208,7 +215,7 @@ namespace VirtualFS.Compressed
{
base.FileOpenRead(path);
return new VirtualStream(this, File.GetInputStream((ZipEntry)_pathToEntry[path].Data));
return new VirtualStream(this, File.GetInputStream((ZipEntry)_pathToEntry[path].Data), path);
}
/// <summary>Performs application-defined tasks associated with

View File

@@ -49,7 +49,7 @@ namespace VirtualFS
/// <param name="forceLocal">Force usage of local file
/// system, to which directory is bound.</param>
/// <returns>Enumeration of entries in this directory.</returns>
public IEnumerable<Entry> GetEntries(Regex mask = null, bool forceLocal = false)
public virtual IEnumerable<Entry> GetEntries(Regex mask = null, bool forceLocal = false)
{
if (FileSystem == null)
throw new InvalidOperationException("This entry has no relation with any file system.");
@@ -60,12 +60,119 @@ namespace VirtualFS
FileSystem.GetEntries(Path, mask);
}
/// <summary>Enumerate directories in this directory.</summary>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <param name="forceLocal">Force usage of local file
/// system, to which directory is bound.</param>
/// <returns>Enumeration of entries in this directory.</returns>
public virtual IEnumerable<Directory> GetDirectories(Regex mask = null, bool forceLocal = false)
{
if (FileSystem == null)
throw new InvalidOperationException("This entry has no relation with any file system.");
// If file system is rooted, check root file system
return !forceLocal && FileSystem.RootFileSystem != null ?
FileSystem.RootFileSystem.GetDirectories(FullPath, mask) :
FileSystem.GetDirectories(Path, mask);
}
/// <summary>Enumerate files in this directory.</summary>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <param name="forceLocal">Force usage of local file
/// system, to which directory is bound.</param>
/// <returns>Enumeration of entries in this directory.</returns>
public virtual IEnumerable<File> GetFiles(Regex mask = null, bool forceLocal = false)
{
if (FileSystem == null)
throw new InvalidOperationException("This entry has no relation with any file system.");
// If file system is rooted, check root file system
return !forceLocal && FileSystem.RootFileSystem != null ?
FileSystem.RootFileSystem.GetFiles(FullPath, mask) :
FileSystem.GetFiles(Path, mask);
}
/// <summary>Check if entry with given name exists.</summary>
/// <param name="name">Name to check.</param>
/// <param name="forceLocal">If set to <c>true</c> force usage of local file system.</param>
/// <returns>Returns <c>true</c> if entry does exist, otherwise <c>false</c>.</returns>
/// <exception cref="System.InvalidOperationException">This entry has no relation with any file system.</exception>
public virtual bool Exists(string name, bool forceLocal = false)
{
if (FileSystem == null)
throw new InvalidOperationException("This entry has no relation with any file system.");
// If file system is rooted, check root file system
return !forceLocal && FileSystem.RootFileSystem != null ?
FileSystem.RootFileSystem.Exists(FullPath + name) :
FileSystem.Exists(Path + name);
}
/// <summary>Get entry located under given path.</summary>
/// <param name="name">Name of entry to get.</param>
/// <param name="forceLocal">If set to <c>true</c> force usage of local file system.</param>
/// <returns>Returns entry information.</returns>
public virtual Entry GetEntry(string name, bool forceLocal = false)
{
if (FileSystem == null)
throw new InvalidOperationException("This entry has no relation with any file system.");
// If file system is rooted, check root file system
return !forceLocal && FileSystem.RootFileSystem != null ?
FileSystem.RootFileSystem.GetEntry(FullPath + name) :
FileSystem.GetEntry(Path + name);
}
/// <summary>Get directory located under given path.</summary>
/// <param name="name">Name of file to get.</param>
/// <param name="forceLocal">If set to <c>true</c> force usage of local file system.</param>
/// <returns>Returns directory entry information.</returns>
public virtual Directory GetDirectory(string name, bool forceLocal = false)
{
Path path = Path + name;
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't get directory from file path '{0}'", path));
return GetEntry(name, forceLocal) as Directory;
}
/// <summary>Get file located under given path.</summary>
/// <param name="name">Name of file to get.</param>
/// <param name="forceLocal">If set to <c>true</c> force usage of local file system.</param>
/// <returns>Returns file entry information.</returns>
public virtual File GetFile(string name, bool forceLocal = false)
{
Path path = Path + name;
if (path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't get file from directory path '{0}'", path));
return GetEntry(name, forceLocal) as File;
}
/// <summary>Get entry in this directory.</summary>
/// <param name="name">Entry name.</param>
/// <param name="forceLocal">Force usage of local file
/// system, to which directory is bound.</param>
/// <returns>Enumeration of entries in this directory.</returns>
public virtual Entry GetEntries(string name, bool forceLocal = false)
{
if (FileSystem == null)
throw new InvalidOperationException("This entry has no relation with any file system.");
// If file system is rooted, check root file system
return !forceLocal && FileSystem.RootFileSystem != null ?
FileSystem.RootFileSystem.GetEntry(FullPath + name) :
FileSystem.GetEntry(Path + name);
}
/// <summary>Create Directory and return new entry.</summary>
/// <param name="name">Name of directory.</param>
/// <param name="forceLocal">Force usage of local file
/// system, to which directory is bound.</param>
/// <returns>Created entry.</returns>
public Directory Create(string name, bool forceLocal = false)
public virtual Directory Create(string name, bool forceLocal = false)
{
return !forceLocal && FileSystem.RootFileSystem != null ?
FileSystem.RootFileSystem.DirectoryCreate(FullPath.Append(string.Format("{0}{1}",
@@ -82,7 +189,7 @@ namespace VirtualFS
/// subdirectories, and files in path; otherwise <c>false</c>.</param>
/// <param name="forceLocal">Force usage of local file
/// system, to which directory is bound.</param>
public void Delete(bool recursive = false, bool forceLocal = false)
public virtual void Delete(bool recursive = false, bool forceLocal = false)
{
if (!forceLocal && FileSystem.RootFileSystem != null)
FileSystem.RootFileSystem.DirectoryDelete(FullPath, recursive);
@@ -96,7 +203,7 @@ namespace VirtualFS
/// system, to which directory is bound.</param>
/// <remarks>Created file is empty.</remarks>
/// <returns>The <see cref="File"/> entry information.</returns>
public File FileCreate(string name, bool forceLocal = false)
public virtual File FileCreate(string name, bool forceLocal = false)
{
if (!forceLocal && FileSystem.RootFileSystem != null)
{
@@ -113,7 +220,7 @@ namespace VirtualFS
var path = Path.Append(
name.Validated("name", invalidChars: Path.InvalidFileChars, invalidSeq: Path.InvalidPathSequences));
using (var stream = FileSystem.FileCreate(path))
using (var stream = FileSystem.FileCreate(path, true))
stream.Close();
return FileSystem.GetEntry(path) as File;

View File

@@ -55,7 +55,7 @@ namespace VirtualFS.EmbeddedResource
{
Assembly = assembly;
var fi = new System.IO.FileInfo(Assembly.Location);
var fi = new System.IO.FileInfo(Uri.UnescapeDataString(new UriBuilder(Assembly.CodeBase).Path));
_pathToResource = Assembly.GetManifestResourceNames()
.Where(x => (string.IsNullOrEmpty(rootNamespace) || x.StartsWith(rootNamespace)) && (filter == null || filter.IsMatch(x)))
@@ -68,6 +68,7 @@ namespace VirtualFS.EmbeddedResource
CreationTime = fi.CreationTimeUtc,
LastAccessTime = fi.LastAccessTime,
LastWriteTime = fi.LastWriteTime,
Size = 0,
})
.ToDictionary(
k => k.Path,
@@ -142,7 +143,7 @@ namespace VirtualFS.EmbeddedResource
if (!ret && path.IsDirectory && _pathToResource.Keys.Any(x => x.IsParentOf(path)))
{
var fi = new System.IO.FileInfo(Assembly.Location);
var fi = new System.IO.FileInfo(Uri.UnescapeDataString(new UriBuilder(Assembly.CodeBase).Path));
_pathToResource.Add(path, new Directory()
{
@@ -181,7 +182,7 @@ namespace VirtualFS.EmbeddedResource
/// should return null if real path can't be determined.</remarks>
public override string EntryRealPath(Path path)
{
return this.Assembly.Location;
return Uri.UnescapeDataString(new UriBuilder(Assembly.CodeBase).Path);
}
/// <summary>Get entries located under given path.</summary>
@@ -209,7 +210,7 @@ namespace VirtualFS.EmbeddedResource
{
base.FileOpenRead(path);
return new VirtualStream(this, Assembly.GetManifestResourceStream(_pathToResource[path].Data.ToString()));
return new VirtualStream(this, Assembly.GetManifestResourceStream(_pathToResource[path].Data.ToString()), path);
}
}
}

View File

@@ -60,6 +60,21 @@ namespace VirtualFS
/// <summary>Gets or sets additional internal data for file system entry.</summary>
internal object Data { get; set; }
/// <summary>Updates information about file.</summary>
public virtual void UpdateInfo()
{
var up = IsDirectory ? FileSystem.GetDirectory(Path) : FileSystem.GetEntry(Path);
if (up != null)
{
IsReadOnly = up.IsReadOnly;
IsDirectory = up.IsDirectory;
CreationTime = up.CreationTime;
LastAccessTime = up.LastAccessTime;
LastWriteTime = up.LastWriteTime;
}
}
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
/// </summary>

View File

@@ -37,8 +37,8 @@ namespace VirtualFS.Extensions
/// </summary>
/// <param name="source">The source string.</param>
/// <param name="desc">A description of the source string to build errors and exceptions if needed.</param>
/// <param name="canbeNull">True if the returned string can be null.</param>
/// <param name="canbeEmpty">True if the returned string can be empty.</param>
/// <param name="canBeNull">True if the returned string can be null.</param>
/// <param name="canBeEmpty">True if the returned string can be empty.</param>
/// <param name="trim">True to trim the returned string.</param>
/// <param name="trimStart">True to left-trim the returned string.</param>
/// <param name="trimEnd">True to right-trim the returned string.</param>
@@ -53,7 +53,7 @@ namespace VirtualFS.Extensions
/// <param name="invalidSeq">If not null, an array containing the sequences that are not allowed.</param>
/// <returns>A new validated string.</returns>
public static string Validated(this string source, string desc = null,
bool canbeNull = false, bool canbeEmpty = false,
bool canBeNull = false, bool canBeEmpty = false,
bool trim = true, bool trimStart = false, bool trimEnd = false,
int minLen = -1, int maxLen = -1, char padLeft = '\0', char padRight = '\0',
char[] invalidChars = null, char[] validChars = null, string[] invalidSeq = null)
@@ -64,7 +64,7 @@ namespace VirtualFS.Extensions
// Validating if null sources are accepted...
if (source == null)
{
if (!canbeNull) throw new ArgumentNullException(desc, string.Format("{0} cannot be null.", desc));
if (!canBeNull) throw new ArgumentNullException(desc, string.Format("{0} cannot be null.", desc));
return null;
}
@@ -92,7 +92,7 @@ namespace VirtualFS.Extensions
// Validating emptyness and lenghts...
if (source.Length == 0)
{
if (!canbeEmpty) throw new ArgumentException(string.Format("{0} cannot be empty.", desc));
if (!canBeEmpty) throw new ArgumentException(string.Format("{0} cannot be empty.", desc));
return string.Empty;
}

View File

@@ -35,6 +35,9 @@ namespace VirtualFS
/// <summary>File representation.</summary>
public class File : Entry
{
/// <summary>Gets file size if it can be determined.</summary>
public virtual long Size { get; internal set; }
private static UTF8Encoding _utfNoBOM = new UTF8Encoding(false, true);
/// <summary>Opens an existing file for reading.</summary>
@@ -85,6 +88,8 @@ namespace VirtualFS
using (Stream fileStream = OpenRead(true))
{
int offset = 0;
if (fileStream.CanSeek)
{
long length = fileStream.Length;
if (length > 2147483647L)
throw new IOException("Can't read file larger than 2GB.");
@@ -102,6 +107,20 @@ namespace VirtualFS
len -= read;
}
}
else
using (MemoryStream ms = new MemoryStream())
{
byte[] buf = new byte[4096];
while (true)
{
int len = fileStream.Read(buf, 0, buf.Length);
if (len == 0) break;
ms.Write(buf, 0, len);
}
array = ms.ToArray();
}
}
return array;
}
@@ -147,7 +166,7 @@ namespace VirtualFS
/// <param name="bytes">The bytes to write to the file.</param>
public void WriteAllBytes(byte[] bytes)
{
using (Stream fileStream = FileSystem.FileCreate(Path))
using (Stream fileStream = FileSystem.FileCreate(Path, true))
fileStream.Write(bytes, 0, bytes.Length);
}
@@ -156,7 +175,7 @@ namespace VirtualFS
/// <param name="contents">The string array to write to the file.</param>
public void WriteAllLines(string[] contents)
{
using (Stream fileStream = FileSystem.FileCreate(Path))
using (Stream fileStream = FileSystem.FileCreate(Path, true))
using (var writer = new StreamWriter(fileStream, _utfNoBOM))
foreach (var item in contents)
writer.WriteLine(item);
@@ -167,7 +186,7 @@ namespace VirtualFS
/// <param name="contents">The string to write to the file.</param>
public void WriteAllText(string contents)
{
using (Stream fileStream = FileSystem.FileCreate(Path))
using (Stream fileStream = FileSystem.FileCreate(Path, true))
using (var writer = new StreamWriter(fileStream, _utfNoBOM))
writer.Write(contents);
}

View File

@@ -0,0 +1,48 @@
/*
* VirtualFS - Virtual File System library.
* Copyright (c) 2016, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace VirtualFS
{
/// <summary>Enum containing file system mount priority</summary>
public enum FileSystemMountPriority
{
/// <summary>Normal mode.</summary>
Normal,
/// <summary>Mount as last.</summary>
Low,
/// <summary>Mount as first.</summary>
High,
}
}

View File

@@ -43,8 +43,8 @@ namespace VirtualFS
/// <summary>Gets or sets a value indicating whether this file system
/// has higher priority than others mounted on the same path.</summary>
/// <remarks>This value must be set corectly before mount.</remarks>
bool HighPriority { get; set; }
/// <remarks>This value must be set correctly before mount.</remarks>
FileSystemMountPriority Priority { get; set; }
/// <summary>Gets a value indicating whether this file system is read only.</summary>
bool IsReadOnly { get; }
@@ -54,6 +54,9 @@ namespace VirtualFS
/// file is open in this file system.</remarks>
bool IsBusy { get; }
/// <summary>Gets or sets name of file system.</summary>
string Name { get; set; }
#region Common
/// <summary>Check if given path exists.</summary>
@@ -67,6 +70,16 @@ namespace VirtualFS
/// <returns>Returns entry information.</returns>
Entry GetEntry(Path path);
/// <summary>Get directory located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <returns>Returns directory entry information.</returns>
Directory GetDirectory(Path path);
/// <summary>Get file located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <returns>Returns file entry information.</returns>
File GetFile(Path path);
/// <summary>Get path to physical file containing entry.</summary>
/// <remarks>This may not work with all file systems. Implementation
/// should return null if real path can't be determined.</remarks>
@@ -107,6 +120,18 @@ namespace VirtualFS
/// <returns>Returns entry information.</returns>
IEnumerable<Entry> GetEntries(Path path, Regex mask = null);
/// <summary>Get directories located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
IEnumerable<Directory> GetDirectories(Path path, Regex mask = null);
/// <summary>Get files located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
IEnumerable<File> GetFiles(Path path, Regex mask = null);
/// <summary>Create directory and return new entry.</summary>
/// <remarks>Parent directory must exist.</remarks>
/// <param name="path">The directory path to create.</param>
@@ -122,6 +147,12 @@ namespace VirtualFS
#region File
/// <summary>Creates empty file on file system and returns it's description.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>File description.</returns>
File FileTouch(Path path, bool overwrite = false);
/// <summary>Opens an existing file for reading.</summary>
/// <param name="path">The file to be opened for reading.</param>
/// <returns>A read-only <see cref="Stream"/> on the specified path.</returns>
@@ -129,15 +160,17 @@ namespace VirtualFS
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <param name="append">Append file.</param>
/// <returns>An unshared <see cref="Stream"/> object on
/// the specified path with write access.</returns>
Stream FileOpenWrite(Path path);
Stream FileOpenWrite(Path path, bool append = false);
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream"/> that provides
/// write access to the file specified in path.</returns>
Stream FileCreate(Path path);
Stream FileCreate(Path path, bool overwrite = false);
/// <summary>Deletes the specified file. An exception is not thrown
/// if the specified file does not exist.</summary>

View File

@@ -123,7 +123,7 @@ namespace VirtualFS.Implementation
if (pathAndFs == null)
throw new InvalidOperationException("File system containing parent directory was not found.");
return pathAndFs.FileSystem.EntryRealPath(path.RemoveParent(parent));
return pathAndFs.FileSystem.EntryRealPath(path.RemoveParent(pathAndFs.Path));
}
/// <summary>Copies an existing file or directory to a new location.</summary>
@@ -215,7 +215,7 @@ namespace VirtualFS.Implementation
if (pathAndFs == null)
throw new InvalidOperationException("Writable file system containing parent directory was not found.");
pathAndFs.FileSystem.ReName(path.RemoveParent(parent), newName);
pathAndFs.FileSystem.ReName(path.RemoveParent(pathAndFs.Path), newName);
}
/// <summary>Get entries located under given path.</summary>
@@ -235,6 +235,40 @@ namespace VirtualFS.Implementation
.Distinct();
}
/// <summary>Get directories located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<Directory> GetDirectories(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries."));
return _mounts
.Where(x => x.Key.IsParentOfOrSame(path))
.SelectMany(v => v.Value.Where(x => x.Exists(path.RemoveParent(v.Key))),
(k, v) => v.GetDirectories(path.RemoveParent(k.Key)))
.SelectMany(e => e, (p, e) => e)
.Distinct();
}
/// <summary>Get files located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<File> GetFiles(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries."));
return _mounts
.Where(x => x.Key.IsParentOfOrSame(path))
.SelectMany(v => v.Value.Where(x => x.Exists(path.RemoveParent(v.Key))),
(k, v) => v.GetFiles(path.RemoveParent(k.Key), mask))
.SelectMany(e => e, (p, e) => e)
.Distinct();
}
/// <summary>Create Directory and return new entry.</summary>
/// <param name="path">Path of directory.</param>
/// <returns>Created entry.</returns>
@@ -255,7 +289,7 @@ namespace VirtualFS.Implementation
if (pathAndFs == null)
throw new InvalidOperationException("Writable file system containing parent directory was not found.");
return pathAndFs.FileSystem.DirectoryCreate(path.RemoveParent(parent));
return pathAndFs.FileSystem.DirectoryCreate(path.RemoveParent(pathAndFs.Path));
}
/// <summary>Deletes the specified directory and, if
@@ -281,7 +315,7 @@ namespace VirtualFS.Implementation
if (pathAndFs == null)
throw new InvalidOperationException("Writable file system containing parent directory was not found.");
pathAndFs.FileSystem.DirectoryDelete(path.RemoveParent(parent));
pathAndFs.FileSystem.DirectoryDelete(path.RemoveParent(pathAndFs.Path));
}
/// <summary>Opens an existing file for reading.</summary>
@@ -297,23 +331,24 @@ namespace VirtualFS.Implementation
throw new InvalidOperationException(string.Format("Can't open directory path ('{0}') as file.", path));
var pathAndFs = _mounts
.Where(x => x.Key.IsParentOfOrSame(parent))
.Where(x => x.Key.IsParentOfOrSame(path))
.SelectMany(v => v.Value, (k, v) => new { Path = k.Key, FileSystem = v })
.FirstOrDefault(pfs => pfs.FileSystem.Exists(parent.RemoveParent(pfs.Path)));
.FirstOrDefault(pfs => pfs.FileSystem.Exists(path.RemoveParent(pfs.Path)));
if (pathAndFs == null)
throw new InvalidOperationException("File system containing parent directory was not found.");
return pathAndFs.FileSystem.FileOpenRead(path.RemoveParent(parent));
return pathAndFs.FileSystem.FileOpenRead(path.RemoveParent(pathAndFs.Path));
}
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <param name="append">Append file.</param>
/// <returns>An unshared <see cref="Stream" /> object on
/// the specified path with write access.</returns>
/// <exception cref="System.InvalidOperationException">Writable
/// file system containing parent directory was not found.</exception>
public override Stream FileOpenWrite(Path path)
public override Stream FileOpenWrite(Path path, bool append = false)
{
Path parent = path.Parent;
@@ -328,16 +363,39 @@ namespace VirtualFS.Implementation
if (pathAndFs == null)
throw new InvalidOperationException("Writable file system containing parent directory was not found.");
return pathAndFs.FileSystem.FileOpenWrite(path.RemoveParent(parent));
return pathAndFs.FileSystem.FileOpenWrite(path.RemoveParent(pathAndFs.Path));
}
/// <summary>Creates empty file on file system and returns it's description.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>File description.</returns>
public override File FileTouch(Path path, bool overwrite = false)
{
Path parent = path.Parent;
if (path.IsDirectory)
throw new InvalidOperationException(string.Format("Can't open directory path ('{0}') as file.", path));
var pathAndFs = _mounts
.Where(x => x.Key.IsParentOfOrSame(parent))
.SelectMany(v => v.Value, (k, v) => new { Path = k.Key, FileSystem = v })
.FirstOrDefault(pfs => !pfs.FileSystem.IsReadOnly && pfs.FileSystem.Exists(parent.RemoveParent(pfs.Path)));
if (pathAndFs == null)
throw new InvalidOperationException("Writable file system containing parent directory was not found.");
return pathAndFs.FileSystem.FileTouch(path.RemoveParent(pathAndFs.Path), overwrite);
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream" /> that provides
/// write access to the file specified in path.</returns>
/// <exception cref="System.InvalidOperationException">Writable
/// file system containing parent directory was not found.</exception>
public override Stream FileCreate(Path path)
public override Stream FileCreate(Path path, bool overwrite = false)
{
Path parent = path.Parent;
@@ -352,7 +410,7 @@ namespace VirtualFS.Implementation
if (pathAndFs == null)
throw new InvalidOperationException("Writable file system containing parent directory was not found.");
return pathAndFs.FileSystem.FileCreate(path.RemoveParent(parent));
return pathAndFs.FileSystem.FileCreate(path.RemoveParent(pathAndFs.Path), overwrite);
}
/// <summary>Files the delete.</summary>
@@ -374,7 +432,7 @@ namespace VirtualFS.Implementation
if (pathAndFs == null)
throw new InvalidOperationException("Writable file system containing parent directory was not found.");
pathAndFs.FileSystem.FileDelete(path.RemoveParent(parent));
pathAndFs.FileSystem.FileDelete(path.RemoveParent(pathAndFs.Path));
}
#endregion IFileSystem implementation
@@ -403,7 +461,7 @@ namespace VirtualFS.Implementation
public bool Mount(IFileSystem fs, Path path)
{
if (!path.IsDirectory)
throw new InvalidOperationException("Path '{0}' is not a directory. You can only mount file system under directories.");
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory. You can only mount file system under directories.", path));
lock (_globalLock)
{
@@ -413,7 +471,15 @@ namespace VirtualFS.Implementation
if (!_mounts.ContainsKey(path))
_mounts.Add(path, new List<IFileSystem>());
_mounts[path].Insert(_mounts[path].Count(f => f.HighPriority), fs);
if (fs.Priority == FileSystemMountPriority.High)
_mounts[path].Insert(0, fs);
else if (fs.Priority == FileSystemMountPriority.Low)
_mounts[path].Add(fs);
else
_mounts[path].Insert(_mounts[path].Count(f => f.Priority == FileSystemMountPriority.High), fs);
if (fs is BaseFileSystem)
((BaseFileSystem)fs).RootFileSystem = this;
}
return true;
@@ -449,6 +515,9 @@ namespace VirtualFS.Implementation
if (_mounts[mountPaths[i]].Count == 0)
_mounts.Remove(mountPaths[i]);
if (fs is BaseFileSystem)
((BaseFileSystem)fs).RootFileSystem = null;
result = true;
}
}

View File

@@ -26,6 +26,7 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.IO;
using VirtualFS.Base;
@@ -36,19 +37,30 @@ namespace VirtualFS.Implementation
public class VirtualStream : Stream
{
/// <summary>Gets the internal stream.</summary>
/// <remarks>Do not close internal stream by yourself.</remarks>
public Stream InternalStream { get; private set; }
/// <summary>Gets the path of file.</summary>
public Path Path { get; private set; }
/// <summary>Gets the file system.</summary>
public IFileSystem FileSystem { get; private set; }
/// <summary>Initializes a new instance of
/// the <see cref="VirtualStream" /> class.</summary>
/// <summary>Gets or sets some unspecified kind of data.</summary>
internal object Unspecified { get; set; }
/// <summary>Occurs when stream is closed.</summary>
public event EventHandler OnClose;
/// <summary>Initializes a new instance of the <see cref="VirtualStream"/> class.</summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="stream">The internal stream.</param>
internal VirtualStream(BaseFileSystem fileSystem, Stream stream)
/// <param name="path">The path of file.</param>
internal VirtualStream(BaseFileSystem fileSystem, Stream stream, Path path)
{
fileSystem.IncreaseOpened();
InternalStream = stream;
Path = path;
FileSystem = fileSystem;
}
@@ -61,6 +73,9 @@ namespace VirtualFS.Implementation
{
((BaseFileSystem)FileSystem).DecreaseOpened();
InternalStream.Close();
if (OnClose != null)
OnClose(this, EventArgs.Empty);
}
#region Wrapped Properties

View File

@@ -197,31 +197,68 @@ namespace VirtualFS.Memory
{
base.FileOpenRead(path);
return new VirtualStream(this, new System.IO.MemoryStream((byte[])(GetEntry(path).Data ?? new byte[] { })));
return new VirtualStream(this, new System.IO.MemoryStream((byte[])(GetEntry(path).Data ?? new byte[] { })), path);
}
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <param name="append">Append file.</param>
/// <returns>An unshared <see cref="Stream" /> object on
/// the specified path with write access.</returns>
public override System.IO.Stream FileOpenWrite(Path path)
public override System.IO.Stream FileOpenWrite(Path path, bool append = false)
{
base.FileOpenWrite(path);
var e = GetEntry(path);
var data = (byte[])(e.Data ?? new byte[] { });
var ms = new System.IO.MemoryStream();
ms.Write(data, 0, data.Length);
return new MemoryWriteVirtualStream(this, (File)e, ms);
if (append)
{
var data = (byte[])(e.Data ?? new byte[] { });
ms.Write(data, 0, data.Length);
}
return new MemoryWriteVirtualStream(this, (File)e, path, ms);
}
/// <summary>Creates empty file on file system and returns it's description.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>File description.</returns>
public override File FileTouch(Path path, bool overwrite = false)
{
base.FileTouch(path, overwrite);
var e = (File)GetEntry(path);
if (e != null)
return e;
else
{
e = new File
{
Path = path,
Data = new byte[] { },
FileSystem = this,
IsReadOnly = IsReadOnly,
CreationTime = DateTime.Now,
LastAccessTime = DateTime.Now,
LastWriteTime = DateTime.Now,
Size = 0,
};
lock (_lock)
_pathToEntry[path] = e;
return e;
}
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream" /> that provides
/// write access to the file specified in path.</returns>
public override System.IO.Stream FileCreate(Path path)
public override System.IO.Stream FileCreate(Path path, bool overwrite = false)
{
base.FileCreate(path);
@@ -239,13 +276,14 @@ namespace VirtualFS.Memory
CreationTime = DateTime.Now,
LastAccessTime = DateTime.Now,
LastWriteTime = DateTime.Now,
Size = 0,
};
lock (_lock)
_pathToEntry[path] = e;
}
return new MemoryWriteVirtualStream(this, e, new System.IO.MemoryStream());
return new MemoryWriteVirtualStream(this, e, path, new System.IO.MemoryStream());
}
/// <summary>Deletes the specified file. An exception is not thrown

View File

@@ -44,9 +44,10 @@ namespace VirtualFS.Memory
/// <see cref="MemoryWriteVirtualStream" /> class.</summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="file">The file to which stream belongs.</param>
/// <param name="path">The path of file.</param>
/// <param name="stream">The internal stream.</param>
internal MemoryWriteVirtualStream(BaseFileSystem fileSystem, File file, MemoryStream stream)
: base(fileSystem, stream)
internal MemoryWriteVirtualStream(BaseFileSystem fileSystem, File file, Path path, MemoryStream stream)
: base(fileSystem, stream, path)
{
File = file;
}
@@ -59,6 +60,7 @@ namespace VirtualFS.Memory
public override void Close()
{
File.Data = ((MemoryStream)InternalStream).ToArray();
File.Size = ((byte[])File.Data).LongLength;
base.Close();
}
}

View File

@@ -27,17 +27,15 @@
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using VirtualFS.Extensions;
namespace VirtualFS
{
/// <summary>
/// Class representing path in file system.
/// </summary>
/// <remarks>
/// Path is always rooted, so you can't make a mistake.
/// </remarks>
/// <summary>Class representing path in file system.</summary>
/// <remarks>Path is always rooted, so you can't make a mistake.</remarks>
public class Path
{
#region Static part
@@ -76,6 +74,40 @@ namespace VirtualFS
Root = new Path();
}
/// <summary>Determines whether the specified path is rooted.</summary>
/// <param name="path">The path to check.</param>
/// <returns>Returns <c>true</c> if the specified path is rooted; otherwise, <c>false</c>.</returns>
public static bool IsRooted(string path)
{
return path[0] == SeparatorChar;
}
/// <summary>Creates the rooted path.</summary>
/// <param name="path">The path.</param>
/// <returns>Path that is rooted in case of passing not rooted path in form of string.</returns>
public static Path CreateRootedPath(string path)
{
return IsRooted(path) ? (Path)path : Root + path;
}
/// <summary>Tries the parse path.</summary>
/// <param name="path">The path.</param>
/// <param name="vfspath">The virtual file system path.</param>
/// <returns>Returns <c>true</c> if the specified path was parsed; otherwise, <c>false</c>.</returns>
public static bool TryParsePath(string path, out Path vfspath)
{
try
{
vfspath = Path.CreateRootedPath(path);
}
catch
{
vfspath = null;
}
return vfspath != null;
}
#endregion Static part
private string _path;
@@ -89,20 +121,41 @@ namespace VirtualFS
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Path"/> class.
/// </summary>
/// <summary>Initializes a new instance of the <see cref="Path"/> class.</summary>
/// <param name="path">The path.</param>
/// <exception cref="System.InvalidOperationException">Path is not rooted.</exception>
public Path(string path)
{
_path = path.Validated("Path", invalidChars: InvalidPathChars, invalidSeq: InvalidPathSequences);
_segments = _path.Split(SeparatorChar).Where(x => !string.IsNullOrEmpty(x)).ToArray();
if (path[0] != SeparatorChar)
throw new InvalidOperationException("Path is not rooted.");
IsDirectory = _path.EndsWith(SeparatorChar.ToString());
List<string> segments = path.Split(SeparatorChar).Where(x => !string.IsNullOrEmpty(x)).ToList();
List<string> finalSegments = new List<string>();
foreach (string segment in segments)
{
if (segment == "." || segment == "")
continue;
else if (segment == "..")
{
if (finalSegments.Any())
finalSegments.RemoveAt(finalSegments.Count - 1);
else
throw new InvalidOperationException("Path tries to leave root.");
continue;
}
//else if (segment.ToArray().Distinct().FirstOrDefault() == '.')
// throw new InvalidOperationException("Path contains too much M&M's.");
finalSegments.Add(segment);
}
IsDirectory = path.EndsWith(SeparatorChar.ToString());
_path = string.Concat(SeparatorChar.ToString(), string.Join(SeparatorChar.ToString(), finalSegments), IsDirectory && finalSegments.Any() ? SeparatorChar.ToString() : string.Empty)
.Validated("Path", invalidChars: InvalidPathChars, invalidSeq: InvalidPathSequences);
_segments = finalSegments.ToArray();
}
/// <summary>Gets a value indicating whether this instance is directory.</summary>
@@ -166,6 +219,22 @@ namespace VirtualFS
return name.Substring(extensionIndex + 1);
}
/// <summary>Gets the file name without extension.</summary>
/// <returns>Return file name without extension as string.</returns>
/// <exception cref="System.ArgumentException">The specified path is not a file.</exception>
public string GetNameWithoutExtension()
{
if (IsDirectory)
throw new ArgumentException("The specified path is not a file.");
string name = _segments.Last();
int extensionIndex = name.LastIndexOf(ExtensionChar);
if (extensionIndex < 0 || extensionIndex == name.Length)
return string.Empty;
return name.Substring(0, extensionIndex);
}
/// <summary>Gets the name of entry from path.</summary>
public string Name { get { return _segments.Length > 0 ? _segments.Last() : null; } }
@@ -203,7 +272,7 @@ namespace VirtualFS
{
if (!parent.IsDirectory)
throw new ArgumentException("The specified path can not be the parent of this path: it is not a directory.");
if (!_path.StartsWith(_path))
if (!_path.StartsWith(parent))
throw new ArgumentException("The specified path is not a parent of this path.");
return new Path(_path.Remove(0, parent._path.Length - 1));
@@ -211,7 +280,26 @@ namespace VirtualFS
internal Path AddParent(Path parent)
{
return parent.ToString().TrimEnd(SeparatorChar) + this;
return string.Concat(parent.ToString().TrimEnd(SeparatorChar), this.ToString());
}
/// <summary>Combine paths.</summary>
/// <param name="components">Array of paths to combine.</param>
/// <returns>Instance of Path</returns>
public static Path Combine(params string[] components)
{
var list = new List<string>();
foreach (var component in components)
if (!string.IsNullOrEmpty(component))
foreach (var c in component.Split(new char[] { SeparatorChar }, StringSplitOptions.RemoveEmptyEntries))
list.Add(c);
var last = components.Where(x => !string.IsNullOrEmpty(x)).LastOrDefault();
if (last == null)
last = string.Empty;
return (string.Join(SeparatorChar.ToString(), list.ToArray()) +
(last.EndsWith(SeparatorChar.ToString()) ? SeparatorChar.ToString() : string.Empty));
}
#region Operators
@@ -220,14 +308,14 @@ namespace VirtualFS
/// <param name="path">The path.</param>
public static implicit operator string(Path path)
{
return path._path;
return path == null ? null : path._path;
}
/// <summary>Convert string to path.</summary>
/// <param name="path">The path.</param>
public static implicit operator Path(string path)
{
return new Path(path);
return path == null ? null : new Path(path);
}
/// <summary>Determines whether the specified <see cref="System.Object" /> is equal to this instance.</summary>

View File

@@ -0,0 +1,378 @@
/*
* VirtualFS - Virtual File System library.
* Copyright (c) 2013, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.FtpClient;
using System.Text.RegularExpressions;
using VirtualFS.Base;
using VirtualFS.Implementation;
namespace VirtualFS.Physical
{
/// <summary>Ftp file system implementation.</summary>
/// <remarks>This is very, very simple implementation
/// of a FTP file system. Use this when regular FTP won't do the job.</remarks>
public class FtpFileSystem : BaseFileSystem
{
private string _ftpServer;
private int _ftpServerPort;
private string _userName;
private string _password;
private string _rootPath;
/// <summary>Gets or sets the connection type, default is
/// AutoPassive which tries
/// a connection with EPSV first and if it fails then tries
/// PASV before giving up. If you know exactly which kind of
/// connection you need you can slightly increase performance
/// by defining a speicific type of passive or active data
/// connection here.</summary>
public FtpDataConnectionType ConnectionType { get; set; }
/// <summary>Initializes a new instance of the <see cref="FtpFileSystem"/> class.</summary>
/// <param name="ftpServer">The FTP server.</param>
/// <param name="userName">FTP User name.</param>
/// <param name="password">FTP password.</param>
/// <param name="rootPath">FTP root path on server.</param>
public FtpFileSystem(Uri ftpServer, string userName, string password, string rootPath)
: base(false)
{
ConnectionType = FtpDataConnectionType.AutoActive;
_ftpServer = ftpServer.Host;
_ftpServerPort = ftpServer.Port;
_userName = userName;
_password = password;
_rootPath = rootPath;
if (_ftpServer.EndsWith("/"))
_ftpServer = _ftpServer.Substring(0, _ftpServer.Length - 1);
if (!string.IsNullOrEmpty(_rootPath) && _rootPath.EndsWith("/"))
_rootPath = _rootPath.Substring(0, _rootPath.Length - 1);
}
/// <summary>Check if given path exists.</summary>
/// <param name="path">Path to check.</param>
/// <returns>Returns <c>true</c> if entry does
/// exist, otherwise <c>false</c>.</returns>
public override bool Exists(Path path)
{
try
{
using (FtpClient conn = GetConnection())
{
if (path.IsDirectory)
return conn.DirectoryExists(GetFtpPath(path));
else
return conn.FileExists(GetFtpPath(path));
}
}
catch (WebException)
{
return false;
}
}
/// <summary>Get entry located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <returns>Returns entry information.</returns>
public override Entry GetEntry(Path path)
{
Entry result = null;
if (Exists(path))
{
if (path.IsDirectory)
result = new Directory();
else
result = new File();
result.IsReadOnly = IsReadOnly;
result.Path = path;
result.FileSystem = this;
}
return result;
}
/// <summary>Get path to physical file containing entry.</summary>
/// <param name="path">Virtual file system path.</param>
/// <returns>Real file system path or <c>null</c> if real
/// path doesn't exist.</returns>
/// <remarks>This may not work with all file systems. Implementation
/// should return null if real path can't be determined.</remarks>
public override string EntryRealPath(Path path)
{
string uri = _ftpServer;
if (!string.IsNullOrEmpty(_rootPath))
uri += _rootPath;
if (path.Parent != null)
uri += (string)path.Parent + (string)path.Name ?? string.Empty;
return uri;
}
/// <summary>Get entries located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<Entry> GetEntries(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries."));
List<Entry> result = new List<Entry>();
using (FtpClient conn = GetConnection())
{
foreach (FtpListItem item in conn
.GetListing(GetFtpPath(path),
FtpListOption.Modify | FtpListOption.Size | FtpListOption.DerefLinks | FtpListOption.AllFiles))
{
switch (item.Type)
{
case FtpFileSystemObjectType.File:
result.Add(new File()
{
IsReadOnly = IsReadOnly,
Path = path + item.Name,
FileSystem = this,
CreationTime = item.Created,
LastWriteTime = item.Modified,
LastAccessTime = item.Modified,
Size = item.Size,
});
break;
case FtpFileSystemObjectType.Directory:
result.Add(new Directory()
{
IsReadOnly = IsReadOnly,
Path = string.Concat(path, item.Name.TrimEnd('/'), "/"),
FileSystem = this,
CreationTime = item.Created,
LastWriteTime = item.Modified,
LastAccessTime = item.Modified,
});
break;
case FtpFileSystemObjectType.Link:
if (item.LinkObject != null)
{
switch (item.LinkObject.Type)
{
case FtpFileSystemObjectType.File:
result.Add(new File()
{
IsReadOnly = IsReadOnly,
Path = path + item.Name,
FileSystem = this,
CreationTime = item.LinkObject.Created,
LastWriteTime = item.LinkObject.Modified,
LastAccessTime = item.LinkObject.Modified,
Size = item.LinkObject.Size,
});
break;
case FtpFileSystemObjectType.Directory:
result.Add(new Directory()
{
IsReadOnly = IsReadOnly,
Path = string.Concat(path, item.Name.TrimEnd('/'), "/"),
FileSystem = this,
CreationTime = item.LinkObject.Created,
LastWriteTime = item.LinkObject.Modified,
LastAccessTime = item.LinkObject.Modified,
});
break;
}
}
break;
}
}
}
return result;
}
/// <summary>Change name of the entry under specified path.</summary>
/// <param name="path">The path to entry which name will be changed.</param>
/// <param name="newName">The new name of entry.</param>
public override void ReName(Path path, string newName)
{
base.ReName(path, newName);
using (FtpClient conn = GetConnection())
conn.Rename(GetFtpPath(path), GetFtpPath(path.Parent + newName));
}
/// <summary>Create Directory and return new entry.</summary>
/// <param name="path">Path of directory.</param>
/// <returns>Created entry.</returns>
public override Directory DirectoryCreate(Path path)
{
base.DirectoryCreate(path);
using (FtpClient conn = GetConnection())
conn.CreateDirectory(GetFtpPath(path), true);
return (Directory)GetEntry(path);
}
/// <summary>Deletes the specified directory and, if
/// indicated, any subdirectories in the directory.</summary>
/// <param name="path">The path of the directory to remove.</param>
/// <param name="recursive">Set <c>true</c> to remove directories,
/// subdirectories, and files in path; otherwise <c>false</c>.</param>
public override void DirectoryDelete(Path path, bool recursive = false)
{
base.DirectoryDelete(path, recursive);
using (FtpClient conn = GetConnection())
conn.DeleteDirectory(GetFtpPath(path));
}
/// <summary>Opens an existing file for reading.</summary>
/// <param name="path">The file to be opened for reading.</param>
/// <returns>A read-only <see cref="Stream" /> on the specified path.</returns>
public override Stream FileOpenRead(Path path)
{
base.FileOpenRead(path);
FtpClient conn = GetConnection();
var ret = new VirtualStream(this, conn.OpenRead(GetFtpPath(path)), path);
ret.Unspecified = conn;
ret.OnClose += (s, e) => ((FtpClient)((VirtualStream)s).Unspecified).Dispose();
return ret;
}
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <param name="append">Append file.</param>
/// <returns>An unshared <see cref="Stream" /> object on
/// the specified path with write access.</returns>
public override Stream FileOpenWrite(Path path, bool append = false)
{
base.FileOpenWrite(path, append);
return FileOpenWritePrivate(path, append);
}
/// <summary>Creates empty file on file system and returns it's description.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>File description.</returns>
public override File FileTouch(Path path, bool overwrite = false)
{
base.FileTouch(path, overwrite);
using (var fs = FileOpenWritePrivate(path, false))
fs.Close();
return new File()
{
IsReadOnly = IsReadOnly,
Path = path,
FileSystem = this,
CreationTime = DateTime.Now,
LastWriteTime = DateTime.Now,
LastAccessTime = DateTime.Now,
Size = 0,
};
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream" /> that provides
/// write access to the file specified in path.</returns>
public override Stream FileCreate(Path path, bool overwrite = false)
{
base.FileCreate(path, overwrite);
return FileOpenWritePrivate(path, false);
}
/// <summary>Deletes the specified file. An exception is not thrown
/// if the specified file does not exist.</summary>
/// <param name="path">The path of the file to be deleted.</param>
public override void FileDelete(Path path)
{
base.FileDelete(path);
using (FtpClient conn = GetConnection())
conn.DeleteFile(GetFtpPath(path));
}
private Stream FileOpenWritePrivate(Path path, bool append)
{
FtpClient conn = GetConnection();
var ret = new VirtualStream(this, append ? conn.OpenAppend(GetFtpPath(path), FtpDataType.Binary) : conn.OpenWrite(GetFtpPath(path), FtpDataType.Binary), path);
ret.Unspecified = conn;
ret.OnClose += (s, e) => ((FtpClient)((VirtualStream)s).Unspecified).Dispose();
return ret;
}
private string GetFtpPath(string path)
{
string fullPath = string.Empty; // _ftpServer;
if (!string.IsNullOrEmpty(_rootPath))
fullPath += _rootPath;
if (path != null)
fullPath += path;
return fullPath;
}
private FtpClient GetConnection()
{
FtpClient conn = new FtpClient();
conn.DataConnectionType = ConnectionType;
conn.Host = _ftpServer;
conn.Port = _ftpServerPort;
conn.Credentials = new NetworkCredential(_userName, _password);
return conn;
}
}
}

View File

@@ -62,7 +62,7 @@ namespace VirtualFS.Physical
return new Directory
{
IsReadOnly = IsReadOnly,
Path = rootPath + string.Format("{0}{1}", fsi.Name, Path.SeparatorChar),
Path = rootPath == null ? Path.Root : (Path)(rootPath + string.Format("{0}{1}", fsi.Name, Path.SeparatorChar)),
FileSystem = this,
CreationTime = fsi.CreationTimeUtc,
LastAccessTime = fsi.LastAccessTime,
@@ -77,6 +77,7 @@ namespace VirtualFS.Physical
CreationTime = fsi.CreationTimeUtc,
LastAccessTime = fsi.LastAccessTime,
LastWriteTime = fsi.LastWriteTime,
Size = (fsi as System.IO.FileInfo).Length,
};
return null;
@@ -108,7 +109,7 @@ namespace VirtualFS.Physical
fsi = new System.IO.FileInfo(EntryRealPath(path));
if (fsi.Exists)
result = FileSystemInfoToEntry(fsi, path);
result = FileSystemInfoToEntry(fsi, path.Parent);
return result;
}
@@ -130,6 +131,36 @@ namespace VirtualFS.Physical
.Select(f => FileSystemInfoToEntry(f, path));
}
/// <summary>Get directories located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<Directory> GetDirectories(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries."));
return new System.IO.DirectoryInfo(EntryRealPath(path))
.EnumerateDirectories()
.Where(f => mask == null || mask.IsMatch(f.Name))
.Select(f => FileSystemInfoToEntry(f, path) as Directory);
}
/// <summary>Get files located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<File> GetFiles(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries."));
return new System.IO.DirectoryInfo(EntryRealPath(path))
.EnumerateFiles()
.Where(f => mask == null || mask.IsMatch(f.Name))
.Select(f => FileSystemInfoToEntry(f, path) as File);
}
/// <summary>Get path to physical file containing entry.</summary>
/// <param name="path">Virtual file system path.</param>
/// <returns>Real file system path or <c>null</c> if real
@@ -218,34 +249,51 @@ namespace VirtualFS.Physical
{
base.FileOpenRead(path);
return new VirtualStream(this, System.IO.File.OpenRead(EntryRealPath(path)));
return new VirtualStream(this, System.IO.File.OpenRead(EntryRealPath(path)), path);
}
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <param name="append">Append file.</param>
/// <returns>An unshared <see cref="Stream" /> object on
/// the specified path with write access.</returns>
public override Stream FileOpenWrite(Path path)
public override Stream FileOpenWrite(Path path, bool append = false)
{
base.FileOpenWrite(path);
base.FileOpenWrite(path, append);
string realPath = EntryRealPath(path);
if (System.IO.File.Exists(realPath))
return new VirtualStream(this, new FileStream(realPath, FileMode.Open, FileAccess.Write, FileShare.Read, 4096, FileOptions.None));
return new VirtualStream(this, new FileStream(realPath, append ? FileMode.Append : FileMode.Open, FileAccess.Write, FileShare.Read, 4096, FileOptions.None), path);
return null;
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <summary>Creates empty file on file system and returns it's description.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <returns>A <see cref="Stream"/> that provides
/// write access to the file specified in path.</returns>
public override Stream FileCreate(Path path)
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>File description.</returns>
public override File FileTouch(Path path, bool overwrite = false)
{
base.FileCreate(path);
base.FileTouch(path, overwrite);
string realPath = EntryRealPath(path);
return new VirtualStream(this, new FileStream(realPath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, 4096));
using (var fs = new FileStream(realPath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, 4096))
fs.Close();
return FileSystemInfoToEntry(new System.IO.FileInfo(realPath), path.Parent) as File;
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream"/> that provides
/// write access to the file specified in path.</returns>
public override Stream FileCreate(Path path, bool overwrite = false)
{
base.FileCreate(path, overwrite);
string realPath = EntryRealPath(path);
return new VirtualStream(this, new FileStream(realPath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, 4096), path);
}
/// <summary>Deletes the specified file. An exception is thrown
@@ -253,7 +301,7 @@ namespace VirtualFS.Physical
/// <param name="path">The path of the file to be deleted.</param>
public override void FileDelete(Path path)
{
base.FileCreate(path);
base.FileDelete(path);
string realPath = EntryRealPath(path);
if (System.IO.File.Exists(realPath))

View File

@@ -0,0 +1,500 @@
/*
* VirtualFS - Virtual File System library.
* Copyright (c) 2021, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using Renci.SshNet;
using Renci.SshNet.Sftp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using VirtualFS.Base;
namespace VirtualFS.Physical
{
public class SFtpFileSystem : BaseFileSystem
{
private string _ftpServer;
private string _userName;
private string _password;
private byte[] _key;
private byte[] _fingerPrint;
private string _rootPath;
private Action<Exception> _exHandler;
private SftpClient _client;
private SftpClient Client
{
get
{
if (_client == null)
_client = CreateClientPrivate();
return _client;
}
}
/// <summary>Initializes a new instance of the <see cref="SFtpFileSystem"/> class.</summary>
/// <param name="host">The SFTP server.</param>
/// <param name="userName">SFTP User name.</param>
/// <param name="password">SFTP password.</param>
/// <param name="key">SFTP private key.</param>
/// <param name="fingerPrint">SFTP server fingerprint.</param>
/// <param name="rootPath">SFTP root path on server.</param>
public SFtpFileSystem(string host, string userName, string password, byte[] key, byte[] fingerPrint, string rootPath, Action<Exception> exHandler = null)
: base(false)
{
_exHandler = exHandler ?? (e => { });
_ftpServer = host;
_userName = userName;
_password = password;
_key = key;
_fingerPrint = fingerPrint;
_rootPath = rootPath;
if (_ftpServer.EndsWith("/"))
_ftpServer = _ftpServer.Substring(0, _ftpServer.Length - 1);
if (!string.IsNullOrEmpty(_rootPath) && _rootPath.EndsWith("/"))
_rootPath = _rootPath.Substring(0, _rootPath.Length - 1);
if (_rootPath == "/")
_rootPath = string.Empty;
}
private SftpClient CreateClientPrivate()
{
var ams = new List<AuthenticationMethod>();
if (!string.IsNullOrEmpty(_password))
ams.Add(new PasswordAuthenticationMethod(_userName, _password));
if (_key != null && _key.Length > 0)
using (var ms = new MemoryStream(_key))
ams.Add(new PrivateKeyAuthenticationMethod(_userName, new PrivateKeyFile(ms)));
var ci = new ConnectionInfo(_ftpServer, _userName, ams.ToArray());
var client = new SftpClient(ci);
if (_fingerPrint != null && _fingerPrint.Length > 0)
client.HostKeyReceived += (sender, e) =>
{
e.CanTrust = true;
if (_fingerPrint.Length == e.FingerPrint.Length)
for (var i = 0; i < _fingerPrint.Length; i++)
if (_fingerPrint[i] != e.FingerPrint[i])
{
e.CanTrust = false;
break;
}
else
e.CanTrust = false;
};
else
client.HostKeyReceived += (sender, e) => e.CanTrust = true;
client.Connect();
return client;
}
private Entry CreateEntry(Path path, ISftpFile ls, bool pathIsParent = true)
{
Entry e = null;
if (ls.IsDirectory)
e = new Directory
{
IsReadOnly = IsReadOnly,
Path = pathIsParent ? (path + (ls.Name + "/")) : path,
FileSystem = this,
Data = ls,
};
else
e = new File
{
CreationTime = ls.LastWriteTime,
LastWriteTime = ls.LastWriteTime,
LastAccessTime = ls.LastAccessTime,
Size = ls.Length,
IsReadOnly = IsReadOnly,
Path = pathIsParent ? (path + ls.Name) : path,
FileSystem = this,
Data = ls,
};
return e;
}
private string AdaptPath(SftpClient c, Path path)
{
var r = path.ToString().Substring(1);
if (!string.IsNullOrEmpty(_rootPath)) {
r = (string)(_rootPath + (string)path);
if (string.IsNullOrEmpty(r))
return null;
r = r[0] == Path.SeparatorChar ? r.Substring(1) : r;
}
var wd = c.WorkingDirectory;
if (!wd.EndsWith(Path.SeparatorChar.ToString()))
wd += Path.SeparatorChar;
return wd + r;
}
/// <summary>Get path to physical file containing entry.</summary>
/// <param name="path">Virtual file system path.</param>
/// <returns>Real file system path or <c>null</c> if real
/// path doesn't exist.</returns>
/// <remarks>This may not work with all file systems. Implementation
/// should return null if real path can't be determined.</remarks>
public override string EntryRealPath(Path path)
{
string uri = _ftpServer;
if (!string.IsNullOrEmpty(_rootPath))
uri += _rootPath;
if (path.Parent != null)
uri += (string)path.Parent + (string)path.Name ?? string.Empty;
return uri;
}
/// <summary>Check if given path exists.</summary>
/// <param name="path">Path to check.</param>
/// <returns>Returns <c>true</c> if entry does
/// exist, otherwise <c>false</c>.</returns>
public override bool Exists(Path path)
{
try
{
return Client.Exists(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
return false;
}
}
/// <summary>Get entries located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<Entry> GetEntries(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries.", path));
List<Entry> result = new List<Entry>();
try
{
var lst = Client.ListDirectory(AdaptPath(Client, path));
foreach (var ls in lst)
{
if (ls.Name == "." || ls.Name == "..")
continue;
Entry e = CreateEntry(path, ls);
if (e != null)
result.Add(e);
}
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return result;
}
/// <summary>Get entry located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <returns>Returns entry information.</returns>
public override Entry GetEntry(Path path)
{
Entry result = null;
try
{
if (Client.Exists(AdaptPath(Client, path)))
result = CreateEntry(path, Client.Get(AdaptPath(Client, path)), false);
}
catch (Exception)
{
}
return result;
}
/// <summary>Change name of the entry under specified path.</summary>
/// <param name="path">The path to entry which name will be changed.</param>
/// <param name="newName">The new name of entry.</param>
public override void ReName(Path path, string newName)
{
base.ReName(path, newName);
try
{
Client.RenameFile(AdaptPath(Client, path), AdaptPath(Client, path.Parent + newName));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
}
/// <summary>Create Directory and return new entry.</summary>
/// <param name="path">Path of directory.</param>
/// <returns>Created entry.</returns>
public override Directory DirectoryCreate(Path path)
{
base.DirectoryCreate(path);
try
{
Client.CreateDirectory(AdaptPath(Client, path));
if (Client.Exists(AdaptPath(Client, path)))
return CreateEntry(path, Client.Get(AdaptPath(Client, path)), false) as Directory;
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Deletes the specified directory and, if
/// indicated, any subdirectories in the directory.</summary>
/// <param name="path">The path of the directory to remove.</param>
/// <param name="recursive">Set <c>true</c> to remove directories,
/// subdirectories, and files in path; otherwise <c>false</c>.</param>
public override void DirectoryDelete(Path path, bool recursive = false)
{
base.DirectoryDelete(path, recursive);
try
{
if (Client.Exists(AdaptPath(Client, path)))
Client.DeleteDirectory(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
}
/// <summary>Opens an existing file for reading.</summary>
/// <param name="path">The file to be opened for reading.</param>
/// <returns>A read-only <see cref="Stream" /> on the specified path.</returns>
public override Stream FileOpenRead(Path path)
{
base.FileOpenRead(path);
try
{
return Client.OpenRead(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <param name="append">Append file.</param>
/// <returns>An unshared <see cref="Stream" /> object on
/// the specified path with write access.</returns>
public override Stream FileOpenWrite(Path path, bool append = false)
{
base.FileOpenWrite(path, append);
try
{
return Client.OpenWrite(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream" /> that provides
/// write access to the file specified in path.</returns>
public override File FileTouch(Path path, bool overwrite = false)
{
base.FileTouch(path, overwrite);
try
{
using (var fs = Client.OpenWrite(AdaptPath(Client, path)))
fs.Close();
return new File
{
CreationTime = DateTime.Now,
LastWriteTime = DateTime.Now,
LastAccessTime = DateTime.Now,
Size = 0,
IsReadOnly = false,
Path = path,
FileSystem = this,
Data = null,
};
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream" /> that provides
/// write access to the file specified in path.</returns>
public override Stream FileCreate(Path path, bool overwrite = false)
{
base.FileCreate(path, overwrite);
try
{
return Client.OpenWrite(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Deletes the specified file. An exception is not thrown
/// if the specified file does not exist.</summary>
/// <param name="path">The path of the file to be deleted.</param>
public override void FileDelete(Path path)
{
base.FileDelete(path);
try
{
if (Client.Exists(AdaptPath(Client, path)))
Client.DeleteFile(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* VirtualFS - Virtual File System library.
* Copyright (c) 2013, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using VirtualFS.Base;
using VirtualFS.Implementation;
namespace VirtualFS.Physical
{
/// <summary>Ftp file system implementation.</summary>
/// <remarks>This is very, very simple implementation
/// of a FTP file system. Use this when regular FTP won't do the job.</remarks>
public class SimpleFtpCachedFileSystem : SimpleFtpFileSystem
{
private Dictionary<Path, List<Entry>> _cache = new Dictionary<Path, List<Entry>>();
/// <summary>Initializes a new instance of the <see cref="FtpFileSystem"/> class.</summary>
/// <param name="ftpServer">The FTP server.</param>
/// <param name="userName">FTP User name.</param>
/// <param name="password">FTP password.</param>
/// <param name="rootPath">FTP root path on server.</param>
public SimpleFtpCachedFileSystem(string ftpServer, string userName, string password, string rootPath)
: base(ftpServer, userName, password, rootPath)
{
}
/// <summary>Check if given path exists.</summary>
/// <param name="path">Path to check.</param>
/// <returns>Returns <c>true</c> if entry does
/// exist, otherwise <c>false</c>.</returns>
public override bool Exists(Path path)
{
try
{
if (path.IsDirectory && path == "/")
return true;
if (path.Parent == null)
return false;
var entries = this.GetEntries(path.Parent);
return entries.Any(x => x.Path == path);
}
catch (WebException)
{
return false;
}
}
/// <summary>Get entries located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<Entry> GetEntries(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries.", path));
List<Entry> cached = null;
if (_cache.ContainsKey(path))
cached = _cache[path];
else
{
cached = base.GetEntries(path, null).ToList();
_cache.Add(path, cached);
}
if (mask == null)
return cached.Select(x => x).ToList();
return cached.Where(x => mask.IsMatch(x.Path.Name)).Select(x => x).ToList();
}
/// <summary>Change name of the entry under specified path.</summary>
/// <param name="path">The path to entry which name will be changed.</param>
/// <param name="newName">The new name of entry.</param>
public override void ReName(Path path, string newName)
{
base.ReName(path, newName);
if (_cache.ContainsKey(path.Parent))
{
var rf = _cache[path.Parent].FirstOrDefault(x => x.Path == path);
if (rf != null)
rf.Path = (path.Parent + newName);
}
}
/// <summary>Create Directory and return new entry.</summary>
/// <param name="path">Path of directory.</param>
/// <returns>Created entry.</returns>
public override Directory DirectoryCreate(Path path)
{
var dir = base.DirectoryCreate(path);
if (_cache.ContainsKey(path.Parent))
{
_cache[path.Parent].Add(new Directory
{
Path = path,
FileSystem = this,
IsReadOnly = IsReadOnly,
});
}
return dir;
}
/// <summary>Deletes the specified directory and, if
/// indicated, any subdirectories in the directory.</summary>
/// <param name="path">The path of the directory to remove.</param>
/// <param name="recursive">Set <c>true</c> to remove directories,
/// subdirectories, and files in path; otherwise <c>false</c>.</param>
public override void DirectoryDelete(Path path, bool recursive = false)
{
base.DirectoryDelete(path, recursive);
if (_cache.ContainsKey(path.Parent))
{
var de = _cache[path.Parent].FirstOrDefault(x => x.Path == path);
if (de != null)
_cache[path.Parent].Remove(de);
}
}
/// <summary>Creates empty file on file system and returns it's description.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>File description.</returns>
public override File FileTouch(Path path, bool overwrite = false)
{
var r = base.FileTouch(path, overwrite);
if (_cache.ContainsKey(path.Parent))
_cache[path.Parent].Add(r);
return r;
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream" /> that provides
/// write access to the file specified in path.</returns>
public override Stream FileCreate(Path path, bool overwrite = false)
{
var str = base.FileCreate(path, overwrite);
if (_cache.ContainsKey(path.Parent))
{
_cache[path.Parent].Add(new File
{
Path = path,
FileSystem = this,
IsReadOnly = IsReadOnly,
});
}
return str;
}
/// <summary>Deletes the specified file. An exception is not thrown
/// if the specified file does not exist.</summary>
/// <param name="path">The path of the file to be deleted.</param>
public override void FileDelete(Path path)
{
base.FileDelete(path);
if (_cache.ContainsKey(path.Parent))
{
var de = _cache[path.Parent].FirstOrDefault(x => x.Path == path);
if (de != null)
_cache[path.Parent].Remove(de);
}
}
}
}

View File

@@ -0,0 +1,376 @@
/*
* VirtualFS - Virtual File System library.
* Copyright (c) 2013, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using VirtualFS.Base;
using VirtualFS.Implementation;
namespace VirtualFS.Physical
{
/// <summary>Ftp file system implementation.</summary>
/// <remarks>This is very, very simple implementation
/// of a FTP file system. Use this when regular FTP won't do the job.</remarks>
public class SimpleFtpFileSystem : BaseFileSystem
{
private string _ftpServer;
private string _userName;
private string _password;
private string _rootPath;
/// <summary>Initializes a new instance of the <see cref="FtpFileSystem"/> class.</summary>
/// <param name="ftpServer">The FTP server.</param>
/// <param name="userName">FTP User name.</param>
/// <param name="password">FTP password.</param>
/// <param name="rootPath">FTP root path on server.</param>
public SimpleFtpFileSystem(string ftpServer, string userName, string password, string rootPath)
: base(false)
{
_ftpServer = ftpServer;
_userName = userName;
_password = password;
_rootPath = rootPath;
if (_ftpServer.EndsWith("/"))
_ftpServer = _ftpServer.Substring(0, _ftpServer.Length - 1);
if (!string.IsNullOrEmpty(_rootPath) && _rootPath.EndsWith("/"))
_rootPath = _rootPath.Substring(0, _rootPath.Length - 1);
}
/// <summary>Check if given path exists.</summary>
/// <param name="path">Path to check.</param>
/// <returns>Returns <c>true</c> if entry does
/// exist, otherwise <c>false</c>.</returns>
public override bool Exists(Path path)
{
try
{
if (path.IsDirectory)
{
FtpDoStuff(WebRequestMethods.Ftp.ListDirectory, path, null);
return true;
}
else
{
bool res = false;
FtpDoStuff(WebRequestMethods.Ftp.ListDirectory, path, null, r =>
{
using (StreamReader sr = new StreamReader(r.GetResponseStream()))
{
string str = sr.ReadLine();
while (str != null)
{
if (string.IsNullOrEmpty(str) || str == "." || str == "..")
{
str = sr.ReadLine();
continue;
}
if (str == path.Name)
{
res = true;
sr.ReadToEnd();
return;
}
}
}
});
return res;
}
}
catch (WebException)
{
return false;
}
}
/// <summary>Get entry located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <returns>Returns entry information.</returns>
public override Entry GetEntry(Path path)
{
Entry result = null;
if (Exists(path))
{
if (path.IsDirectory)
result = new Directory();
else
result = new File();
result.IsReadOnly = IsReadOnly;
result.Path = path;
result.FileSystem = this;
}
return result;
}
/// <summary>Get path to physical file containing entry.</summary>
/// <param name="path">Virtual file system path.</param>
/// <returns>Real file system path or <c>null</c> if real
/// path doesn't exist.</returns>
/// <remarks>This may not work with all file systems. Implementation
/// should return null if real path can't be determined.</remarks>
public override string EntryRealPath(Path path)
{
string uri = _ftpServer;
if (!string.IsNullOrEmpty(_rootPath))
uri += _rootPath;
if (path.Parent != null)
uri += (string)path.Parent + (string)path.Name ?? string.Empty;
return uri;
}
/// <summary>Get entries located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<Entry> GetEntries(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries.", path));
List<Entry> result = new List<Entry>();
FtpDoStuff(WebRequestMethods.Ftp.ListDirectory, path, null, r =>
{
using (StreamReader sr = new StreamReader(r.GetResponseStream()))
{
string str = sr.ReadLine();
while (str != null)
{
if (string.IsNullOrEmpty(str) || str == "." || str == "..")
{
str = sr.ReadLine();
continue;
}
if (mask == null || mask.IsMatch(str))
{
// Here lies a monster. Do not try to understand... just accept.
if (str.Contains("."))
result.Add(new File()
{
IsReadOnly = IsReadOnly,
Path = path + str,
FileSystem = this,
});
else
result.Add(new Directory()
{
IsReadOnly = IsReadOnly,
Path = string.Concat(path, str, "/"),
FileSystem = this,
});
}
str = sr.ReadLine();
}
}
});
return result;
}
/// <summary>Change name of the entry under specified path.</summary>
/// <param name="path">The path to entry which name will be changed.</param>
/// <param name="newName">The new name of entry.</param>
public override void ReName(Path path, string newName)
{
base.ReName(path, newName);
System.Net.FtpWebRequest ftp = (System.Net.FtpWebRequest)System.Net.FtpWebRequest.Create(EntryRealPath(path));
ftp.Credentials = new System.Net.NetworkCredential(_userName, _password);
ftp.Method = WebRequestMethods.Ftp.Rename;
ftp.RenameTo = newName;
using (var r = ftp.GetResponse())
r.Close();
}
/// <summary>Create Directory and return new entry.</summary>
/// <param name="path">Path of directory.</param>
/// <returns>Created entry.</returns>
public override Directory DirectoryCreate(Path path)
{
base.DirectoryCreate(path);
if (FtpDoStuff(WebRequestMethods.Ftp.MakeDirectory, path) == FtpStatusCode.PathnameCreated)
return (Directory)GetEntry(path);
return null;
}
/// <summary>Deletes the specified directory and, if
/// indicated, any subdirectories in the directory.</summary>
/// <param name="path">The path of the directory to remove.</param>
/// <param name="recursive">Set <c>true</c> to remove directories,
/// subdirectories, and files in path; otherwise <c>false</c>.</param>
public override void DirectoryDelete(Path path, bool recursive = false)
{
base.DirectoryDelete(path, recursive);
FtpDoStuff(WebRequestMethods.Ftp.RemoveDirectory, path);
}
/// <summary>Opens an existing file for reading.</summary>
/// <param name="path">The file to be opened for reading.</param>
/// <returns>A read-only <see cref="Stream" /> on the specified path.</returns>
public override Stream FileOpenRead(Path path)
{
base.FileOpenRead(path);
System.Net.FtpWebRequest ftp = (System.Net.FtpWebRequest)System.Net.FtpWebRequest.Create(EntryRealPath(path));
ftp.Credentials = new System.Net.NetworkCredential(_userName, _password);
ftp.Method = System.Net.WebRequestMethods.Ftp.DownloadFile;
ftp.KeepAlive = true;
var resp = ftp.GetResponse();
var ret = new VirtualStream(this, resp.GetResponseStream(), path);
ret.Unspecified = resp;
ret.OnClose += (s, e) =>
{
using (var r = (FtpWebResponse)((VirtualStream)s).Unspecified)
r.Close();
};
return ret;
}
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <param name="append">Append file.</param>
/// <returns>An unshared <see cref="Stream" /> object on
/// the specified path with write access.</returns>
public override Stream FileOpenWrite(Path path, bool append = false)
{
base.FileOpenWrite(path, append);
return FileOpenWritePrivate(path, append);
}
/// <summary>Creates empty file on file system and returns it's description.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>File description.</returns>
public override File FileTouch(Path path, bool overwrite = false)
{
base.FileTouch(path, overwrite);
using (var fs = FileOpenWritePrivate(path, false))
fs.Close();
return new File()
{
IsReadOnly = IsReadOnly,
Path = path,
FileSystem = this,
CreationTime = DateTime.Now,
LastWriteTime = DateTime.Now,
LastAccessTime = DateTime.Now,
Size = 0,
};
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream" /> that provides
/// write access to the file specified in path.</returns>
public override Stream FileCreate(Path path, bool overwrite = false)
{
base.FileCreate(path, overwrite);
return FileOpenWritePrivate(path, false);
}
/// <summary>Deletes the specified file. An exception is not thrown
/// if the specified file does not exist.</summary>
/// <param name="path">The path of the file to be deleted.</param>
public override void FileDelete(Path path)
{
base.FileDelete(path);
FtpDoStuff(WebRequestMethods.Ftp.DeleteFile, path.Parent, path.Name);
}
private Stream FileOpenWritePrivate(Path path, bool append)
{
System.Net.FtpWebRequest ftp = (System.Net.FtpWebRequest)System.Net.FtpWebRequest.Create(EntryRealPath(path));
ftp.Credentials = new System.Net.NetworkCredential(_userName, _password);
ftp.Method = append ? WebRequestMethods.Ftp.AppendFile : WebRequestMethods.Ftp.UploadFile;
ftp.KeepAlive = true;
ftp.UseBinary = true;
var ret = new VirtualStream(this, ftp.GetRequestStream(), path);
ret.Unspecified = ftp;
ret.OnClose += (s, e) =>
{
var req = (FtpWebRequest)((VirtualStream)s).Unspecified;
using (var resp = req.GetResponse())
resp.Close();
};
return ret;
}
internal FtpStatusCode FtpDoStuff(string method, string path, string filename = null, Action<FtpWebResponse> act = null)
{
string uri = _ftpServer;
if (!string.IsNullOrEmpty(_rootPath))
uri += _rootPath;
if (path != null)
uri += path + filename ?? string.Empty;
System.Net.FtpWebRequest ftp = (System.Net.FtpWebRequest)System.Net.FtpWebRequest.Create(uri);
ftp.Credentials = new System.Net.NetworkCredential(_userName, _password);
ftp.Method = method;
ftp.KeepAlive = true;
using (var resp = (FtpWebResponse)ftp.GetResponse())
{
if (act != null)
act(resp);
return resp.StatusCode;
}
}
}
}

View File

@@ -21,6 +21,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="SSH.NET" Version="2024.0.0" />
<PackageReference Include="System.Net.FtpClient" Version="1.0.5824.34026" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net4')) AND '$(MSBuildRuntimeType)' == 'Core' AND '$(OS)' != 'Windows_NT'">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All" />