diff --git a/VirtualFS/Base/BaseFileSystem.cs b/VirtualFS/Base/BaseFileSystem.cs
index f560821..fff498e 100644
--- a/VirtualFS/Base/BaseFileSystem.cs
+++ b/VirtualFS/Base/BaseFileSystem.cs
@@ -49,7 +49,7 @@ namespace VirtualFS.Base
public BaseFileSystem(bool readOnly)
{
IsReadOnly = readOnly;
- HighPriority = false;
+ Priority = FileSystemMountPriority.Normal;
}
/// Gets reference to root file system.
@@ -57,8 +57,8 @@ namespace VirtualFS.Base
/// Gets or sets a value indicating whether this file system
/// has higher priority than others mounted on the same path.
- /// This value must be set corectly before mount.
- public virtual bool HighPriority { get; set; }
+ /// This value must be set correctly before mount.
+ public virtual FileSystemMountPriority Priority { get; set; }
///
/// Gets a value indicating whether this file system is read only.
@@ -82,25 +82,42 @@ namespace VirtualFS.Base
}
}
- ///
- /// Check if given path exists.
- ///
+ /// Gets or sets name of file system.
+ public virtual string Name { get; set; }
+
+ /// Check if given path exists.
/// Path to check.
- ///
- /// Returns true if entry does
- /// exist, otherwise false.
- ///
+ /// Returns true if entry does
+ /// exist, otherwise false.
public abstract bool Exists(Path path);
- ///
- /// Get entry located under given path.
- ///
+ /// Get entry located under given path.
/// Path to get.
- ///
- /// Returns entry information.
- ///
+ /// Returns entry information.
public abstract Entry GetEntry(Path path);
+ /// Get directory located under given path.
+ /// The path.
+ /// Returns directory entry information.
+ 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;
+ }
+
+ /// Get file located under given path.
+ /// Path to get.
+ /// Returns file entry information.
+ 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;
+ }
+
/// Get path to physical file containing entry.
/// Virtual file system path.
/// Real file system path or null 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 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);
}
/// Change name of the entry under specified path.
@@ -255,6 +272,36 @@ namespace VirtualFS.Base
///
public abstract IEnumerable GetEntries(Path path, Regex mask = null);
+ ///
+ /// Get directories located under given path.
+ ///
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ ///
+ /// Returns entry information.
+ ///
+ public virtual IEnumerable GetDirectories(Path path, Regex mask = null)
+ {
+ return GetEntries(path, mask)
+ .Where(e => e is Directory)
+ .Cast();
+ }
+
+ ///
+ /// Get files located under given path.
+ ///
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ ///
+ /// Returns entry information.
+ ///
+ public virtual IEnumerable GetFiles(Path path, Regex mask = null)
+ {
+ return GetEntries(path, mask)
+ .Where(e => e is File)
+ .Cast();
+ }
+
///
/// Create Directory and return new entry.
///
@@ -305,43 +352,11 @@ namespace VirtualFS.Base
throw new InvalidOperationException("Can't delete directory that contains entries.");
}
- /// Opens an existing file for reading.
- /// The file to be opened for reading.
- /// A read-only on the specified path.
- 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;
- }
-
- /// Opens an existing file for writing.
- /// The file to be opened for writing.
- /// An unshared object on
- /// the specified path with write access.
- 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;
- }
-
- /// Creates or overwrites a file in the specified path.
+ /// Creates empty file on file system and returns it's description.
/// The path and name of the file to create.
- /// A that provides
- /// write access to the file specified in path.
- public virtual Stream FileCreate(Path path)
+ /// If file exists replace it.
+ /// File description.
+ 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;
+ }
+
+ /// Opens an existing file for reading.
+ /// The file to be opened for reading.
+ /// A read-only on the specified path.
+ 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;
+ }
+
+ /// Opens an existing file for writing.
+ /// The file to be opened for writing.
+ /// Append file.
+ /// An unshared object on
+ /// the specified path with write access.
+ 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;
+ }
+
+ /// Creates or overwrites a file in the specified path.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// A that provides
+ /// write access to the file specified in path.
+ 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;
}
diff --git a/VirtualFS/Compressed/ZipReadFileSystem.cs b/VirtualFS/Compressed/ZipReadFileSystem.cs
index 7fc7b08..9b1dd8e 100644
--- a/VirtualFS/Compressed/ZipReadFileSystem.cs
+++ b/VirtualFS/Compressed/ZipReadFileSystem.cs
@@ -43,7 +43,7 @@ namespace VirtualFS.Compressed
/// Gets the Zip file.
public ZipFile File { get; private set; }
- private Dictionary _pathToEntry;
+ private Dictionary _pathToEntry = new Dictionary();
/// Gets the assembly on which file system operates.
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);
}
/// Performs application-defined tasks associated with
diff --git a/VirtualFS/Directory.cs b/VirtualFS/Directory.cs
index f59ef4a..f1d77f6 100644
--- a/VirtualFS/Directory.cs
+++ b/VirtualFS/Directory.cs
@@ -49,7 +49,7 @@ namespace VirtualFS
/// Force usage of local file
/// system, to which directory is bound.
/// Enumeration of entries in this directory.
- public IEnumerable GetEntries(Regex mask = null, bool forceLocal = false)
+ public virtual IEnumerable 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);
}
+ /// Enumerate directories in this directory.
+ /// Mask to filter out unwanted entries.
+ /// Force usage of local file
+ /// system, to which directory is bound.
+ /// Enumeration of entries in this directory.
+ public virtual IEnumerable 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);
+ }
+
+ /// Enumerate files in this directory.
+ /// Mask to filter out unwanted entries.
+ /// Force usage of local file
+ /// system, to which directory is bound.
+ /// Enumeration of entries in this directory.
+ public virtual IEnumerable 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);
+ }
+
+ /// Check if entry with given name exists.
+ /// Name to check.
+ /// If set to true force usage of local file system.
+ /// Returns true if entry does exist, otherwise false.
+ /// This entry has no relation with any file system.
+ 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);
+ }
+
+ /// Get entry located under given path.
+ /// Name of entry to get.
+ /// If set to true force usage of local file system.
+ /// Returns entry information.
+ 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);
+ }
+
+ /// Get directory located under given path.
+ /// Name of file to get.
+ /// If set to true force usage of local file system.
+ /// Returns directory entry information.
+ 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;
+ }
+
+ /// Get file located under given path.
+ /// Name of file to get.
+ /// If set to true force usage of local file system.
+ /// Returns file entry information.
+ 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;
+ }
+
+ /// Get entry in this directory.
+ /// Entry name.
+ /// Force usage of local file
+ /// system, to which directory is bound.
+ /// Enumeration of entries in this directory.
+ 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);
+ }
+
/// Create Directory and return new entry.
/// Name of directory.
/// Force usage of local file
/// system, to which directory is bound.
/// Created entry.
- 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 false.
/// Force usage of local file
/// system, to which directory is bound.
- 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.
/// Created file is empty.
/// The entry information.
- 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;
diff --git a/VirtualFS/EmbeddedResource/EmbeddedResourceFileSystem.cs b/VirtualFS/EmbeddedResource/EmbeddedResourceFileSystem.cs
index 0b75345..4226b96 100644
--- a/VirtualFS/EmbeddedResource/EmbeddedResourceFileSystem.cs
+++ b/VirtualFS/EmbeddedResource/EmbeddedResourceFileSystem.cs
@@ -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.
public override string EntryRealPath(Path path)
{
- return this.Assembly.Location;
+ return Uri.UnescapeDataString(new UriBuilder(Assembly.CodeBase).Path);
}
/// Get entries located under given path.
@@ -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);
}
}
}
\ No newline at end of file
diff --git a/VirtualFS/Entry.cs b/VirtualFS/Entry.cs
index 482ade2..50f6bd5 100644
--- a/VirtualFS/Entry.cs
+++ b/VirtualFS/Entry.cs
@@ -60,6 +60,21 @@ namespace VirtualFS
/// Gets or sets additional internal data for file system entry.
internal object Data { get; set; }
+ /// Updates information about file.
+ 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;
+ }
+ }
+
///
/// Determines whether the specified is equal to this instance.
///
diff --git a/VirtualFS/Extensions/Validators.cs b/VirtualFS/Extensions/Validators.cs
index 16b352e..d64df88 100644
--- a/VirtualFS/Extensions/Validators.cs
+++ b/VirtualFS/Extensions/Validators.cs
@@ -37,8 +37,8 @@ namespace VirtualFS.Extensions
///
/// The source string.
/// A description of the source string to build errors and exceptions if needed.
- /// True if the returned string can be null.
- /// True if the returned string can be empty.
+ /// True if the returned string can be null.
+ /// True if the returned string can be empty.
/// True to trim the returned string.
/// True to left-trim the returned string.
/// True to right-trim the returned string.
@@ -53,7 +53,7 @@ namespace VirtualFS.Extensions
/// If not null, an array containing the sequences that are not allowed.
/// A new validated string.
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;
}
diff --git a/VirtualFS/File.cs b/VirtualFS/File.cs
index 5f9e23a..34c7624 100644
--- a/VirtualFS/File.cs
+++ b/VirtualFS/File.cs
@@ -35,6 +35,9 @@ namespace VirtualFS
/// File representation.
public class File : Entry
{
+ /// Gets file size if it can be determined.
+ public virtual long Size { get; internal set; }
+
private static UTF8Encoding _utfNoBOM = new UTF8Encoding(false, true);
/// Opens an existing file for reading.
@@ -85,22 +88,38 @@ namespace VirtualFS
using (Stream fileStream = OpenRead(true))
{
int offset = 0;
- long length = fileStream.Length;
- if (length > 2147483647L)
- throw new IOException("Can't read file larger than 2GB.");
-
- int len = (int)length;
- array = new byte[len];
-
- while (len > 0)
+ if (fileStream.CanSeek)
{
- int read = fileStream.Read(array, offset, len);
- if (read == 0)
- throw new EndOfStreamException("Can't read beyond file stream.");
+ long length = fileStream.Length;
+ if (length > 2147483647L)
+ throw new IOException("Can't read file larger than 2GB.");
- offset += read;
- len -= read;
+ int len = (int)length;
+ array = new byte[len];
+
+ while (len > 0)
+ {
+ int read = fileStream.Read(array, offset, len);
+ if (read == 0)
+ throw new EndOfStreamException("Can't read beyond file stream.");
+
+ offset += read;
+ 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
/// The bytes to write to the file.
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
/// The string array to write to the file.
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
/// The string to write to the file.
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);
}
diff --git a/VirtualFS/FileSystemMountPriority.cs b/VirtualFS/FileSystemMountPriority.cs
new file mode 100644
index 0000000..3083ace
--- /dev/null
+++ b/VirtualFS/FileSystemMountPriority.cs
@@ -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
+{
+ /// Enum containing file system mount priority
+ public enum FileSystemMountPriority
+ {
+ /// Normal mode.
+ Normal,
+
+ /// Mount as last.
+ Low,
+
+ /// Mount as first.
+ High,
+ }
+}
diff --git a/VirtualFS/IFileSystem.cs b/VirtualFS/IFileSystem.cs
index 5decf55..15a9099 100644
--- a/VirtualFS/IFileSystem.cs
+++ b/VirtualFS/IFileSystem.cs
@@ -43,8 +43,8 @@ namespace VirtualFS
/// Gets or sets a value indicating whether this file system
/// has higher priority than others mounted on the same path.
- /// This value must be set corectly before mount.
- bool HighPriority { get; set; }
+ /// This value must be set correctly before mount.
+ FileSystemMountPriority Priority { get; set; }
/// Gets a value indicating whether this file system is read only.
bool IsReadOnly { get; }
@@ -54,6 +54,9 @@ namespace VirtualFS
/// file is open in this file system.
bool IsBusy { get; }
+ /// Gets or sets name of file system.
+ string Name { get; set; }
+
#region Common
/// Check if given path exists.
@@ -67,6 +70,16 @@ namespace VirtualFS
/// Returns entry information.
Entry GetEntry(Path path);
+ /// Get directory located under given path.
+ /// Path to get.
+ /// Returns directory entry information.
+ Directory GetDirectory(Path path);
+
+ /// Get file located under given path.
+ /// Path to get.
+ /// Returns file entry information.
+ File GetFile(Path path);
+
/// Get path to physical file containing entry.
/// This may not work with all file systems. Implementation
/// should return null if real path can't be determined.
@@ -107,6 +120,18 @@ namespace VirtualFS
/// Returns entry information.
IEnumerable GetEntries(Path path, Regex mask = null);
+ /// Get directories located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ IEnumerable GetDirectories(Path path, Regex mask = null);
+
+ /// Get files located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ IEnumerable GetFiles(Path path, Regex mask = null);
+
/// Create directory and return new entry.
/// Parent directory must exist.
/// The directory path to create.
@@ -122,6 +147,12 @@ namespace VirtualFS
#region File
+ /// Creates empty file on file system and returns it's description.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// File description.
+ File FileTouch(Path path, bool overwrite = false);
+
/// Opens an existing file for reading.
/// The file to be opened for reading.
/// A read-only on the specified path.
@@ -129,15 +160,17 @@ namespace VirtualFS
/// Opens an existing file for writing.
/// The file to be opened for writing.
+ /// Append file.
/// An unshared object on
/// the specified path with write access.
- Stream FileOpenWrite(Path path);
+ Stream FileOpenWrite(Path path, bool append = false);
/// Creates or overwrites a file in the specified path.
/// The path and name of the file to create.
+ /// If file exists replace it.
/// A that provides
/// write access to the file specified in path.
- Stream FileCreate(Path path);
+ Stream FileCreate(Path path, bool overwrite = false);
/// Deletes the specified file. An exception is not thrown
/// if the specified file does not exist.
diff --git a/VirtualFS/Implementation/RootFileSystem.cs b/VirtualFS/Implementation/RootFileSystem.cs
index b0eba6b..abab18a 100644
--- a/VirtualFS/Implementation/RootFileSystem.cs
+++ b/VirtualFS/Implementation/RootFileSystem.cs
@@ -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));
}
/// Copies an existing file or directory to a new location.
@@ -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);
}
/// Get entries located under given path.
@@ -235,6 +235,40 @@ namespace VirtualFS.Implementation
.Distinct();
}
+ /// Get directories located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ public override IEnumerable 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();
+ }
+
+ /// Get files located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ public override IEnumerable 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();
+ }
+
/// Create Directory and return new entry.
/// Path of directory.
/// Created entry.
@@ -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));
}
/// 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));
}
/// Opens an existing file for reading.
@@ -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));
}
/// Opens an existing file for writing.
/// The file to be opened for writing.
+ /// Append file.
/// An unshared object on
/// the specified path with write access.
/// Writable
/// file system containing parent directory was not found.
- 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));
+ }
+
+ /// Creates empty file on file system and returns it's description.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// File description.
+ 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);
}
/// Creates or overwrites a file in the specified path.
/// The path and name of the file to create.
+ /// If file exists replace it.
/// A that provides
/// write access to the file specified in path.
/// Writable
/// file system containing parent directory was not found.
- 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);
}
/// Files the delete.
@@ -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());
- _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;
}
}
diff --git a/VirtualFS/Implementation/VirtualStream.cs b/VirtualFS/Implementation/VirtualStream.cs
index 2d0f9d7..80d9560 100644
--- a/VirtualFS/Implementation/VirtualStream.cs
+++ b/VirtualFS/Implementation/VirtualStream.cs
@@ -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
{
/// Gets the internal stream.
+ /// Do not close internal stream by yourself.
public Stream InternalStream { get; private set; }
+ /// Gets the path of file.
+ public Path Path { get; private set; }
+
/// Gets the file system.
public IFileSystem FileSystem { get; private set; }
- /// Initializes a new instance of
- /// the class.
+ /// Gets or sets some unspecified kind of data.
+ internal object Unspecified { get; set; }
+
+ /// Occurs when stream is closed.
+ public event EventHandler OnClose;
+
+ /// Initializes a new instance of the class.
/// The file system.
/// The internal stream.
- internal VirtualStream(BaseFileSystem fileSystem, Stream stream)
+ /// The path of file.
+ 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
diff --git a/VirtualFS/Libraries/ICSharpCode.SharpZipLib.dll b/VirtualFS/Libraries/ICSharpCode.SharpZipLib.dll
deleted file mode 100644
index fe643eb..0000000
Binary files a/VirtualFS/Libraries/ICSharpCode.SharpZipLib.dll and /dev/null differ
diff --git a/VirtualFS/Memory/MemoryFileSystem.cs b/VirtualFS/Memory/MemoryFileSystem.cs
index ffd4f87..3e870e6 100644
--- a/VirtualFS/Memory/MemoryFileSystem.cs
+++ b/VirtualFS/Memory/MemoryFileSystem.cs
@@ -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);
}
/// Opens an existing file for writing.
/// The file to be opened for writing.
+ /// Append file.
/// An unshared object on
/// the specified path with write access.
- 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);
+ }
+
+ /// Creates empty file on file system and returns it's description.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// File description.
+ 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;
+ }
}
/// Creates or overwrites a file in the specified path.
/// The path and name of the file to create.
+ /// If file exists replace it.
/// A that provides
/// write access to the file specified in path.
- 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());
}
/// Deletes the specified file. An exception is not thrown
diff --git a/VirtualFS/Memory/MemoryWriteVirtualStream.cs b/VirtualFS/Memory/MemoryWriteVirtualStream.cs
index e33ab8f..40e94d8 100644
--- a/VirtualFS/Memory/MemoryWriteVirtualStream.cs
+++ b/VirtualFS/Memory/MemoryWriteVirtualStream.cs
@@ -44,9 +44,10 @@ namespace VirtualFS.Memory
/// class.
/// The file system.
/// The file to which stream belongs.
+ /// The path of file.
/// The internal stream.
- 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();
}
}
diff --git a/VirtualFS/Path.cs b/VirtualFS/Path.cs
index 7eb5fd4..77fcf80 100644
--- a/VirtualFS/Path.cs
+++ b/VirtualFS/Path.cs
@@ -27,17 +27,15 @@
*/
using System;
+using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using VirtualFS.Extensions;
namespace VirtualFS
{
- ///
- /// Class representing path in file system.
- ///
- ///
- /// Path is always rooted, so you can't make a mistake.
- ///
+ /// Class representing path in file system.
+ /// Path is always rooted, so you can't make a mistake.
public class Path
{
#region Static part
@@ -76,6 +74,40 @@ namespace VirtualFS
Root = new Path();
}
+ /// Determines whether the specified path is rooted.
+ /// The path to check.
+ /// Returns true if the specified path is rooted; otherwise, false.
+ public static bool IsRooted(string path)
+ {
+ return path[0] == SeparatorChar;
+ }
+
+ /// Creates the rooted path.
+ /// The path.
+ /// Path that is rooted in case of passing not rooted path in form of string.
+ public static Path CreateRootedPath(string path)
+ {
+ return IsRooted(path) ? (Path)path : Root + path;
+ }
+
+ /// Tries the parse path.
+ /// The path.
+ /// The virtual file system path.
+ /// Returns true if the specified path was parsed; otherwise, false.
+ 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
{
}
- ///
- /// Initializes a new instance of the class.
- ///
+ /// Initializes a new instance of the class.
/// The path.
/// Path is not rooted.
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 segments = path.Split(SeparatorChar).Where(x => !string.IsNullOrEmpty(x)).ToList();
+ List finalSegments = new List();
+
+ 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();
}
/// Gets a value indicating whether this instance is directory.
@@ -166,6 +219,22 @@ namespace VirtualFS
return name.Substring(extensionIndex + 1);
}
+ /// Gets the file name without extension.
+ /// Return file name without extension as string.
+ /// The specified path is not a file.
+ 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);
+ }
+
/// Gets the name of entry from path.
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());
+ }
+
+ /// Combine paths.
+ /// Array of paths to combine.
+ /// Instance of Path
+ public static Path Combine(params string[] components)
+ {
+ var list = new List();
+ 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
/// The path.
public static implicit operator string(Path path)
{
- return path._path;
+ return path == null ? null : path._path;
}
/// Convert string to path.
/// The path.
public static implicit operator Path(string path)
{
- return new Path(path);
+ return path == null ? null : new Path(path);
}
/// Determines whether the specified is equal to this instance.
diff --git a/VirtualFS/Physical/FtpFileSystem.cs b/VirtualFS/Physical/FtpFileSystem.cs
new file mode 100644
index 0000000..5afcef2
--- /dev/null
+++ b/VirtualFS/Physical/FtpFileSystem.cs
@@ -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
+{
+ /// Ftp file system implementation.
+ /// This is very, very simple implementation
+ /// of a FTP file system. Use this when regular FTP won't do the job.
+ public class FtpFileSystem : BaseFileSystem
+ {
+ private string _ftpServer;
+ private int _ftpServerPort;
+ private string _userName;
+ private string _password;
+ private string _rootPath;
+
+ /// 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.
+ public FtpDataConnectionType ConnectionType { get; set; }
+
+ /// Initializes a new instance of the class.
+ /// The FTP server.
+ /// FTP User name.
+ /// FTP password.
+ /// FTP root path on server.
+ 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);
+ }
+
+ /// Check if given path exists.
+ /// Path to check.
+ /// Returns true if entry does
+ /// exist, otherwise false.
+ 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;
+ }
+ }
+
+ /// Get entry located under given path.
+ /// Path to get.
+ /// Returns entry information.
+ 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;
+ }
+
+ /// Get path to physical file containing entry.
+ /// Virtual file system path.
+ /// Real file system path or null if real
+ /// path doesn't exist.
+ /// This may not work with all file systems. Implementation
+ /// should return null if real path can't be determined.
+ 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;
+ }
+
+ /// Get entries located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ public override IEnumerable 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 result = new List();
+
+ 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;
+ }
+
+ /// Change name of the entry under specified path.
+ /// The path to entry which name will be changed.
+ /// The new name of entry.
+ public override void ReName(Path path, string newName)
+ {
+ base.ReName(path, newName);
+
+ using (FtpClient conn = GetConnection())
+ conn.Rename(GetFtpPath(path), GetFtpPath(path.Parent + newName));
+ }
+
+ /// Create Directory and return new entry.
+ /// Path of directory.
+ /// Created entry.
+ public override Directory DirectoryCreate(Path path)
+ {
+ base.DirectoryCreate(path);
+
+ using (FtpClient conn = GetConnection())
+ conn.CreateDirectory(GetFtpPath(path), true);
+
+ return (Directory)GetEntry(path);
+ }
+
+ /// Deletes the specified directory and, if
+ /// indicated, any subdirectories in the directory.
+ /// The path of the directory to remove.
+ /// Set true to remove directories,
+ /// subdirectories, and files in path; otherwise false.
+ public override void DirectoryDelete(Path path, bool recursive = false)
+ {
+ base.DirectoryDelete(path, recursive);
+
+ using (FtpClient conn = GetConnection())
+ conn.DeleteDirectory(GetFtpPath(path));
+ }
+
+ /// Opens an existing file for reading.
+ /// The file to be opened for reading.
+ /// A read-only on the specified path.
+ 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;
+ }
+
+ /// Opens an existing file for writing.
+ /// The file to be opened for writing.
+ /// Append file.
+ /// An unshared object on
+ /// the specified path with write access.
+ public override Stream FileOpenWrite(Path path, bool append = false)
+ {
+ base.FileOpenWrite(path, append);
+
+ return FileOpenWritePrivate(path, append);
+ }
+
+ /// Creates empty file on file system and returns it's description.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// File description.
+ 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,
+ };
+ }
+
+ /// Creates or overwrites a file in the specified path.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// A that provides
+ /// write access to the file specified in path.
+ public override Stream FileCreate(Path path, bool overwrite = false)
+ {
+ base.FileCreate(path, overwrite);
+
+ return FileOpenWritePrivate(path, false);
+ }
+
+ /// Deletes the specified file. An exception is not thrown
+ /// if the specified file does not exist.
+ /// The path of the file to be deleted.
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/VirtualFS/Physical/PhysicalFileSystem.cs b/VirtualFS/Physical/PhysicalFileSystem.cs
index d535b01..864c343 100644
--- a/VirtualFS/Physical/PhysicalFileSystem.cs
+++ b/VirtualFS/Physical/PhysicalFileSystem.cs
@@ -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));
}
+ /// Get directories located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ public override IEnumerable 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);
+ }
+
+ /// Get files located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ public override IEnumerable 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);
+ }
+
/// Get path to physical file containing entry.
/// Virtual file system path.
/// Real file system path or null 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);
}
/// Opens an existing file for writing.
/// The file to be opened for writing.
- /// An unshared object on
+ /// Append file.
+ /// An unshared object on
/// the specified path with write access.
- 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;
}
- /// Creates or overwrites a file in the specified path.
+ /// Creates empty file on file system and returns it's description.
/// The path and name of the file to create.
- /// A that provides
- /// write access to the file specified in path.
- public override Stream FileCreate(Path path)
+ /// If file exists replace it.
+ /// File description.
+ 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;
+ }
+
+ /// Creates or overwrites a file in the specified path.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// A that provides
+ /// write access to the file specified in path.
+ 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);
}
/// Deletes the specified file. An exception is thrown
@@ -253,7 +301,7 @@ namespace VirtualFS.Physical
/// The path of the file to be deleted.
public override void FileDelete(Path path)
{
- base.FileCreate(path);
+ base.FileDelete(path);
string realPath = EntryRealPath(path);
if (System.IO.File.Exists(realPath))
diff --git a/VirtualFS/Physical/SFtpFileSystem.cs b/VirtualFS/Physical/SFtpFileSystem.cs
new file mode 100644
index 0000000..994a654
--- /dev/null
+++ b/VirtualFS/Physical/SFtpFileSystem.cs
@@ -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 _exHandler;
+ private SftpClient _client;
+
+ private SftpClient Client
+ {
+ get
+ {
+ if (_client == null)
+ _client = CreateClientPrivate();
+
+ return _client;
+ }
+ }
+
+ /// Initializes a new instance of the class.
+ /// The SFTP server.
+ /// SFTP User name.
+ /// SFTP password.
+ /// SFTP private key.
+ /// SFTP server fingerprint.
+ /// SFTP root path on server.
+ public SFtpFileSystem(string host, string userName, string password, byte[] key, byte[] fingerPrint, string rootPath, Action 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();
+
+ 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;
+ }
+
+ /// Get path to physical file containing entry.
+ /// Virtual file system path.
+ /// Real file system path or null if real
+ /// path doesn't exist.
+ /// This may not work with all file systems. Implementation
+ /// should return null if real path can't be determined.
+ 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;
+ }
+
+ /// Check if given path exists.
+ /// Path to check.
+ /// Returns true if entry does
+ /// exist, otherwise false.
+ 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;
+ }
+ }
+
+ /// Get entries located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ public override IEnumerable 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 result = new List();
+
+ 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;
+ }
+
+ /// Get entry located under given path.
+ /// Path to get.
+ /// Returns entry information.
+ 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;
+ }
+
+ /// Change name of the entry under specified path.
+ /// The path to entry which name will be changed.
+ /// The new name of entry.
+ 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;
+ }
+ }
+ }
+
+
+ /// Create Directory and return new entry.
+ /// Path of directory.
+ /// Created entry.
+ 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;
+ }
+
+ /// Deletes the specified directory and, if
+ /// indicated, any subdirectories in the directory.
+ /// The path of the directory to remove.
+ /// Set true to remove directories,
+ /// subdirectories, and files in path; otherwise false.
+ 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;
+ }
+ }
+ }
+
+ /// Opens an existing file for reading.
+ /// The file to be opened for reading.
+ /// A read-only on the specified path.
+ 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;
+ }
+
+ /// Opens an existing file for writing.
+ /// The file to be opened for writing.
+ /// Append file.
+ /// An unshared object on
+ /// the specified path with write access.
+ 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;
+ }
+
+ /// Creates or overwrites a file in the specified path.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// A that provides
+ /// write access to the file specified in path.
+ 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;
+ }
+
+ /// Creates or overwrites a file in the specified path.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// A that provides
+ /// write access to the file specified in path.
+ 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;
+ }
+
+
+ /// Deletes the specified file. An exception is not thrown
+ /// if the specified file does not exist.
+ /// The path of the file to be deleted.
+ 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;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/VirtualFS/Physical/SimpleFtpCachedFileSystem.cs b/VirtualFS/Physical/SimpleFtpCachedFileSystem.cs
new file mode 100644
index 0000000..3911cba
--- /dev/null
+++ b/VirtualFS/Physical/SimpleFtpCachedFileSystem.cs
@@ -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
+{
+ /// Ftp file system implementation.
+ /// This is very, very simple implementation
+ /// of a FTP file system. Use this when regular FTP won't do the job.
+ public class SimpleFtpCachedFileSystem : SimpleFtpFileSystem
+ {
+ private Dictionary> _cache = new Dictionary>();
+
+ /// Initializes a new instance of the class.
+ /// The FTP server.
+ /// FTP User name.
+ /// FTP password.
+ /// FTP root path on server.
+ public SimpleFtpCachedFileSystem(string ftpServer, string userName, string password, string rootPath)
+ : base(ftpServer, userName, password, rootPath)
+ {
+ }
+
+ /// Check if given path exists.
+ /// Path to check.
+ /// Returns true if entry does
+ /// exist, otherwise false.
+ 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;
+ }
+ }
+
+ /// Get entries located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ public override IEnumerable 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 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();
+ }
+
+ /// Change name of the entry under specified path.
+ /// The path to entry which name will be changed.
+ /// The new name of entry.
+ 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);
+ }
+ }
+
+ /// Create Directory and return new entry.
+ /// Path of directory.
+ /// Created entry.
+ 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;
+ }
+
+ /// Deletes the specified directory and, if
+ /// indicated, any subdirectories in the directory.
+ /// The path of the directory to remove.
+ /// Set true to remove directories,
+ /// subdirectories, and files in path; otherwise false.
+ 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);
+ }
+ }
+
+ /// Creates empty file on file system and returns it's description.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// File description.
+ 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;
+ }
+
+ /// Creates or overwrites a file in the specified path.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// A that provides
+ /// write access to the file specified in path.
+ 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;
+ }
+
+ /// Deletes the specified file. An exception is not thrown
+ /// if the specified file does not exist.
+ /// The path of the file to be deleted.
+ 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/VirtualFS/Physical/SimpleFtpFileSystem.cs b/VirtualFS/Physical/SimpleFtpFileSystem.cs
new file mode 100644
index 0000000..a2f52d2
--- /dev/null
+++ b/VirtualFS/Physical/SimpleFtpFileSystem.cs
@@ -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
+{
+ /// Ftp file system implementation.
+ /// This is very, very simple implementation
+ /// of a FTP file system. Use this when regular FTP won't do the job.
+ public class SimpleFtpFileSystem : BaseFileSystem
+ {
+ private string _ftpServer;
+ private string _userName;
+ private string _password;
+ private string _rootPath;
+
+ /// Initializes a new instance of the class.
+ /// The FTP server.
+ /// FTP User name.
+ /// FTP password.
+ /// FTP root path on server.
+ 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);
+ }
+
+ /// Check if given path exists.
+ /// Path to check.
+ /// Returns true if entry does
+ /// exist, otherwise false.
+ 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;
+ }
+ }
+
+ /// Get entry located under given path.
+ /// Path to get.
+ /// Returns entry information.
+ 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;
+ }
+
+ /// Get path to physical file containing entry.
+ /// Virtual file system path.
+ /// Real file system path or null if real
+ /// path doesn't exist.
+ /// This may not work with all file systems. Implementation
+ /// should return null if real path can't be determined.
+ 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;
+ }
+
+ /// Get entries located under given path.
+ /// Path to get.
+ /// Mask to filter out unwanted entries.
+ /// Returns entry information.
+ public override IEnumerable 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 result = new List();
+
+ 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;
+ }
+
+ /// Change name of the entry under specified path.
+ /// The path to entry which name will be changed.
+ /// The new name of entry.
+ 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();
+ }
+
+ /// Create Directory and return new entry.
+ /// Path of directory.
+ /// Created entry.
+ public override Directory DirectoryCreate(Path path)
+ {
+ base.DirectoryCreate(path);
+
+ if (FtpDoStuff(WebRequestMethods.Ftp.MakeDirectory, path) == FtpStatusCode.PathnameCreated)
+ return (Directory)GetEntry(path);
+
+ return null;
+ }
+
+ /// Deletes the specified directory and, if
+ /// indicated, any subdirectories in the directory.
+ /// The path of the directory to remove.
+ /// Set true to remove directories,
+ /// subdirectories, and files in path; otherwise false.
+ public override void DirectoryDelete(Path path, bool recursive = false)
+ {
+ base.DirectoryDelete(path, recursive);
+
+ FtpDoStuff(WebRequestMethods.Ftp.RemoveDirectory, path);
+ }
+
+ /// Opens an existing file for reading.
+ /// The file to be opened for reading.
+ /// A read-only on the specified path.
+ 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;
+ }
+
+ /// Opens an existing file for writing.
+ /// The file to be opened for writing.
+ /// Append file.
+ /// An unshared object on
+ /// the specified path with write access.
+ public override Stream FileOpenWrite(Path path, bool append = false)
+ {
+ base.FileOpenWrite(path, append);
+
+ return FileOpenWritePrivate(path, append);
+ }
+
+ /// Creates empty file on file system and returns it's description.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// File description.
+ 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,
+ };
+ }
+
+ /// Creates or overwrites a file in the specified path.
+ /// The path and name of the file to create.
+ /// If file exists replace it.
+ /// A that provides
+ /// write access to the file specified in path.
+ public override Stream FileCreate(Path path, bool overwrite = false)
+ {
+ base.FileCreate(path, overwrite);
+
+ return FileOpenWritePrivate(path, false);
+ }
+
+ /// Deletes the specified file. An exception is not thrown
+ /// if the specified file does not exist.
+ /// The path of the file to be deleted.
+ 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 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/VirtualFS/VirtualFS.csproj b/VirtualFS/VirtualFS.csproj
index 66b682f..03558f9 100644
--- a/VirtualFS/VirtualFS.csproj
+++ b/VirtualFS/VirtualFS.csproj
@@ -21,6 +21,8 @@
+
+