lib/pione/util/ftp-server.rb
module Pione
module Util
# FTPAuthInfo is a class for FTP authentication information.
class FTPAuthInfo
attr_reader :user
attr_reader :password
def initialize(user=nil, password=nil)
@user = user || Util::UUID.generate[0...12]
@password = password || Util::UUID.generate[0...12]
end
def to_userinfo
"%s:%s" % [@user, @password]
end
end
class FTPFileSystem
# Return true if the path is a directory or a file.
#
# @param path [Pathname]
# the path
# @return [Boolean]
# true if the path is a directory or a file
def exist?(path)
directory?(path) or file?(path)
end
# Return true if the path is a directory.
#
# @param path [Pathname]
# the path
# @return [Boolean]
# true if the path is a directory
def directory?(path)
raise NotImplemented
end
# Return true if the path is a file.
#
# @param path [Pathname]
# the path
# @return [Boolean]
# true if the path is a file
def file?(path)
raise NotImplemented
end
# Return content of the file.
#
# @param path [Pathname]
# the path
# @return [String]
# file content
def get_file(path)
raise NotImplemented
end
# Put the data into the path and return the byte size.
#
# @param path [Pathname]
# the path
# @param data [Pathname]
# data file path
# @return [Integer]
# byte size of the data
def put_file(path, data)
raise NotImplemented
end
# Delete the file.
#
# @param path [Pathname]
# the path
# @return [void]
def delete_file(path)
raise NotImplemented
end
# Return byte size of the path.
#
# @param path [Pathname]
# the path
# @return [Integer]
# byte size
def get_size(path)
raise NotImplemented
end
# Return mtime of the path. If the path doesn't exist, return nil.
#
# @param path [Pathname]
# the path
# @return [Time]
# mtime
def get_mtime(path)
raise NotImplemented
end
# Return entries of the directory.
#
# @param path [Pathname]
# the path
# @return [Pathname]
# entry names
def entries(path)
raise NotImplemented
end
# Make a directory at the path.
#
# @param path [Pathname]
# the path
# @return [void]
def mkdir(path)
raise NotImplemented
end
# Delete a directory at the path.
#
# @param path [Pathname]
# the path
# @return [void]
def rmdir(path)
raise NotImplemented
end
# Move file.
#
# @param from_path [Pathname]
# from path
# @param to_path [Pathname]
# to path
# @return [void]
def mv(from_path, to_path)
raise NotImplemented
end
end
# OnMemoryFS is a virtual file system on memory.
class FTPOnMemoryFS < FTPFileSystem
ROOT = Pathname.new("/")
attr_reader :directory
attr_reader :file
attr_reader :mtime
def initialize
@directory = {ROOT => Set.new}
@file = {}
@mtime = {}
end
# Clear file system items.
#
# @return [void]
def clear
@directory.clear
@directory[ROOT] = Set.new
@file.clear
@mtime.clear
end
def directory?(path)
@directory.has_key?(path)
end
def file?(path)
@file.has_key?(path)
end
def get_file(path)
@file[path]
end
def put_file(path, data)
@directory[path.dirname] << path.basename
@file[path] = File.read(data)
@mtime[path] = Time.now
end
def delete_file(path)
@directory[path.dirname].delete(path.basename)
@file.delete(path)
@mtime.delete(path)
end
def get_size(path)
@file[path].bytesize
end
def get_mtime(path)
@mtime[path]
end
def entries(path)
@directory[path]
end
def mkdir(path)
@directory[path] = Set.new
unless path == path.dirname
@directory[path.dirname] << path.basename
end
end
def rmdir(path)
@directory.delete(path)
unless path == path.dirname
@directory[path.dirname].delete(path.basename)
end
end
def mv(from_path, to_path)
@directory[to_path.dirname] << from_path.basename
@directory[from_path.dirname].delete(from_path.basename)
@file[to_path] = @file[from_path]
@file.delete(from_path)
@mtime[to_path] = @file[from_path]
@mtime.delete(to_path)
end
end
class FTPLocalFS < FTPFileSystem
attr_reader :directory
attr_reader :file
attr_reader :mtime
def initialize(base)
@base = base
end
def directory?(path)
merge(path).directory?
end
def file?(path)
merge(path).file?
end
def get_file(path)
merge(path).read
end
def put_file(path, data)
Location[data].copy(merge(path))
end
def delete_file(path)
merge(path).delete
end
def get_size(path)
merge(path).size
end
def get_mtime(path)
merge(path).mtime
end
def entries(path)
merge(path).entries.map{|entry| Pathname.new(entry.basename)}
end
def mkdir(path)
merge(path).path.mkdir
end
def rmdir(path)
merge(path).path.rmdir
end
def mv(from_path, to_path)
merge(from_path).path.rename(merge(to_path).path)
end
private
def merge(path)
@base + path.relative_path_from(Pathname.new("/"))
end
end
class FTPServer
@auth_info = FTPAuthInfo.new
@port = 39123
class << self
attr_accessor :auth_info
attr_accessor :fs
attr_accessor :port
attr_reader :thread
# Start FTP server.
def start(fs)
@fs = fs
@thread = Thread.new do
EventMachine.run do
@sig = EventMachine.start_server("0.0.0.0", @port, EM::FTPD::Server, self)
end
end
end
def stop
EventMachine.stop_event_loop
EventMachine.reactor_thread.join
@thread.kill if @thread
end
def make_location(path)
Location["ftp://%s:%s@localhost:%i%s" % [@auth_info.user, @auth_info.password, @port, path]]
end
end
forward! :class, :auth_info, :fs, :port
# Change directory.
def change_dir(path, &b)
path = Pathname.new(path).cleanpath
yield fs.directory?(path)
end
# Return entries of the directory.
def dir_contents(path, &b)
path = Pathname.new(path).cleanpath
if fs.directory?(path)
entries = fs.entries(path).map do |entry|
entry_path = path + entry
if fs.directory?(entry_path)
dir_item(entry)
else
file_item(entry, fs.get_size(entry_path))
end
end
yield entries
else
yield Set.new
end
end
# Authenticate the user.
def authenticate(user, password, &b)
yield auth_info.user == user && auth_info.password == password
end
# Get byte size of the path.
def bytes(path, &b)
path = Pathname.new(path).cleanpath
if fs.file?(path)
yield fs.get_size(path)
elsif fs.directory?(path)
yield -1
else
yield false
end
end
# Get file content of the path.
def get_file(path, &block)
path = Pathname.new(path).cleanpath
if fs.file?(path)
yield fs.get_file(path)
else
yield false
end
end
# Put data to the path.
def put_file(path, data, &b)
path = Pathname.new(path).cleanpath
dir = path.dirname
filename = path.basename
if fs.directory?(dir) and filename
fs.put_file(path, data)
yield data.size
else
yield false
end
end
# Delete file of the path.
def delete_file(path, &b)
path = Pathname.new(path).cleanpath
dir = path.dirname
filename = path.basename
if fs.directory?(dir) and fs.entries(dir).include?(filename)
fs.delete_file(path)
yield true
else
yield false
end
end
# Rename the file.
def rename_file(from, to, &b)
from_path = Pathname.new(from).cleanpath
from_dir = from_path.dirname
from_filename = from_path.basename
to_path = Pathname.new(to).cleanpath
to_dir = to_path.dirname
to_filename = to_path.basename
if fs.file?(from_path) && fs.directory?(to_dir)
data = fs.get_file(from_path)
fs.delete_file(from_path)
fs.put_file(to_path, data)
yield true
else
yield false
end
end
# Make the directory.
def make_dir(path, &b)
path = Pathname.new(path).cleanpath
dir = path.dirname
if fs.exist?(path) or not(fs.directory?(dir))
yield false
else
fs.mkdir(path)
yield true
end
end
# Delete the directory.
def delete_dir(path, &b)
path = Pathname.new(path).cleanpath
if fs.directory?(path) and fs.entries(path).empty?
fs.rmdir(path)
yield true
else
yield false
end
end
# Return the mtime.
def mtime(path)
path = Pathname.new(path).cleanpath
if mtime = fs.get_mtime(path)
yield mtime
else
yield false
end
end
def rename(from_path, to_path, &b)
from_path = Pathname.new(from_path).cleanpath
to_path = Pathname.new(to_path).cleanpath
if fs.file?(from_path) and fs.directory?(to_path.dirname)
fs.mv(from_path, to_path)
yield true
else
yield false
end
end
private
def dir_item(name)
EM::FTPD::DirectoryItem.new(:name => name, :directory => true, :size => 0)
end
def file_item(name, bytes)
EM::FTPD::DirectoryItem.new(:name => name, :directory => false, :size => bytes)
end
end
end
end