Created
May 18, 2016 00:20
-
-
Save coderanger/fcb8c29cf3331c43c3f12c1aa465645d to your computer and use it in GitHub Desktop.
File transport API sketch.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Remote file or directory proxy object. | |
# | |
# @since 1.0.0 | |
class File | |
# Create a proxy object. All data is lazy-loaded so this does very little. | |
# | |
# @param connection [Airlift::Connection] Connection object to use for | |
# operations. | |
# @param path [String] File or directory path. | |
# @param follow_symlink [Boolean] Follow symlinks when getting file info. | |
def initialize(connection, path, follow_symlink: true) | |
@connection = connection | |
@path = path | |
@follow_symlink = follow_symlink | |
end | |
# @!group Accessors | |
# ================= | |
# @attribute connection | |
# Connection object to use for operations. | |
# @return [Airlift::Connection] | |
attr_reader :connection | |
# @attribute path | |
# File or directory path. | |
# @return [String] | |
attr_reader :path | |
# @attribute follow_symlink | |
# Follow symlinks when getting file info. | |
# @return [Boolean] | |
attr_reader :follow_symlink | |
# @!group Core API | |
# ================ | |
# Load file content in to a string. Returns nil if the file does not exist | |
# or is not readable. | |
# | |
# @return [String, nil] | |
def content | |
return @content if defined?(@content) | |
buf = '' | |
if @connection.download_file(@path) {|data| buf << data } | |
@content = buf | |
else | |
# File wasn't readable. | |
@content = nil | |
end | |
end | |
# Upload new file content from a string. | |
# | |
# @param data [String] New file content. | |
# @return [void] | |
def content=(data) | |
@content = data | |
@connection.upload_file(@path) {|send| send.call(data) } | |
end | |
# Gather file metadata information. Returns nil if the file does not exist | |
# or is not readable. | |
# | |
# @return [Airlift::Stat, nil] | |
def stat | |
return @stat if defined?(@stat) | |
@stat = @connection.stat_file(@path, follow_symlink: @follow_symlink) | |
end | |
# Download to a local path. Returns true if the file was downloaded and | |
# false if the file does not exist or is not readable. | |
# | |
# @param local_path [String] Path to download to. | |
# @return [Boolean] | |
def download(local_path) | |
local_file = ::File.open(local_path, 'wb') | |
@connection.download_file(@path) {|data| local_file.write(data) } | |
end | |
# Upload from a local file. Returns true if the file was uploaded and false | |
# if the file could not be written. | |
# | |
# @param local_path [String] Path to upload from. | |
# @return [Boolean] | |
def upload(local_path) | |
@connection.upload_file(@path) do |send| | |
local_file = ::File.open(local_path, 'rb') | |
while data = local_file.read(1024) | |
send.call(data) | |
end | |
end | |
end | |
# Synchronize a local and remote directory. Any new files in the local path | |
# will be uploaded and extra files on the remote side will be removed. | |
# Returns true if the sync succeeds and false if it fails. | |
# | |
# @param local_path [String] Path to sync from. | |
# @return [Boolean] | |
def sync(local_path) | |
@connect.sync_files(local_path, @path) | |
end | |
# @!group Exception API | |
# ===================== | |
# Load file content or raise an exception. | |
# | |
# @see #content | |
def content! | |
content.tap do |data| | |
raise Error::FileError.new("Unable to get content for #{@path}") if data.nil? | |
end | |
end | |
# TODO come up with a good name for this. content!= is not a valid method name. | |
#def content!= | |
#end | |
# Gather file information or raise an exception. | |
# | |
# @see #stat | |
def stat! | |
stat.tap do |data| | |
raise Error::FileError.new("Unable to stat #{@path}") if data.nil? | |
end | |
end | |
# Download file or raise an exception. | |
# | |
# @see #download | |
def download!(local_path) | |
download(local_path).tap do |success| | |
raise Error::FileError.new("Unable to download #{@path}") unless success | |
end | |
end | |
# Upload file or raise an exception. | |
# | |
# @see #upload | |
def upload!(local_path) | |
upload(local_path).tap do |success| | |
raise Error::FileError.new("Unable to upload #{@path}") unless success | |
end | |
end | |
# Synchronize a local and remote directory or raise an exception. | |
# | |
# @see #sync | |
def sync!(local_path) | |
stat(local_path).tap do |success| | |
raise Error::FileError.new("Unable to sync #{@path}") unless success | |
end | |
end | |
# @!group Sugar Helpers | |
# ===================== | |
# Return the proxy corresponding to the unfollowed symlink source for the | |
# same path. | |
# | |
# @return [Airlift::File] | |
def link_source | |
if @follow_symlink | |
self.class.new(@connection, @path, follow_symlink: false) | |
else | |
self | |
end | |
end | |
# Check if this is a normal file. | |
# | |
# @return [Boolean] | |
def file? | |
stat && stat[:type] == :file | |
end | |
# Check if this is a directory. | |
# | |
# @return [Boolean] | |
def directory? | |
stat && stat[:type] == :directory | |
end | |
# Check if this is a symlink. | |
# | |
# @return [Boolean] | |
def symlink? | |
source_stat = source.stat | |
source_stat && source_stat[:type] == :symlink | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment