344 lines
13 KiB
C#
344 lines
13 KiB
C#
/*
|
|
* 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 FluentFTP;
|
|
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.AllFiles))
|
|
{
|
|
switch (item.Type)
|
|
{
|
|
case FtpObjectType.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 FtpObjectType.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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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.Host = _ftpServer;
|
|
conn.Port = _ftpServerPort;
|
|
conn.Credentials = new NetworkCredential(_userName, _password);
|
|
conn.Config.DataConnectionType = ConnectionType;
|
|
conn.Connect();
|
|
|
|
return conn;
|
|
}
|
|
}
|
|
}
|