Files
VirtualFS/VirtualFS/Physical/FtpFileSystem.cs

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;
}
}
}