class Bdsync::Core

Attributes

data_path[R]

Public Class Methods

new(params, sync_type) click to toggle source
# File lib/bdsync/core.rb, line 9
def initialize params, sync_type
    raise "local path not specified"    if !params["local_root_path"]
    raise "remote path not specified"   if !params["remote_root_path"]

    @local_root_path    = params["local_root_path"]
    @remote_root_path   = params["remote_root_path"]
    @infinite_loop      = params["infinite_loop"]
    @data_path          = "#{Dir.home}/.bdsync/#{sync_type}_#{Utils.md5 params.to_s}.yaml"
end
options() click to toggle source
# File lib/bdsync/core.rb, line 19
def self.options
    ["local_root_path:", "remote_root_path:", "infinite_loop"]
end

Public Instance Methods

do_first_time_sync_from_local(relative_path, local_path, remote_path, remote, type) click to toggle source
# File lib/bdsync/core.rb, line 192
def do_first_time_sync_from_local relative_path, local_path, remote_path, remote, type
    case type
    when :file
        if !remote
            remote = upload_file local_path, remote_path
            update_file_data relative_path, local_path, remote.mtime
        elsif remote.directory?
            handle_remote_conflict remote_path
            remote = upload_file local_path, remote_path
            update_file_data relative_path, local_path, remote.mtime
        else
            # compare file contents, conflict if not equal
            if !is_same_contents local_path, remote_path
                handle_remote_conflict remote_path
                remote = upload_file local_path, remote_path
            end

            update_file_data relative_path, local_path, remote.mtime
        end
    when :directory
        if !remote
            remote = remote_mkdir remote_path
            update_directory_data relative_path, local_path, remote.mtime
        elsif remote.directory?
            update_directory_data relative_path, local_path, remote.mtime
        else
            handle_remote_conflict remote_path
            remote = remote_mkdir remote_path
            update_directory_data relative_path, local_path, remote.mtime
        end
    end
end
do_first_time_sync_from_remote(relative_path, local_path, remote_path, remote, type) click to toggle source
# File lib/bdsync/core.rb, line 159
def do_first_time_sync_from_remote relative_path, local_path, remote_path, remote, type
    case type
    when :file
        if !File.exist? local_path
            download_file local_path, remote_path
            update_file_data relative_path, local_path, remote.mtime
        elsif File.directory? local_path
            handle_local_conflict local_path
            download_file local_path, remote_path
            update_file_data relative_path, local_path, remote.mtime
        else
            # compare file contents, conflict if not equal
            if !is_same_contents local_path, remote_path
                handle_local_conflict local_path
                download_file local_path, remote_path
            end

            update_file_data relative_path, local_path, remote.mtime
        end
    when :directory
        if !File.exist? local_path
            local_mkdir local_path
            update_directory_data relative_path, local_path, remote.mtime
        elsif File.directory? local_path
            update_directory_data relative_path, local_path, remote.mtime
        else
            handle_local_conflict local_path
            local_mkdir local_path
            update_directory_data relative_path, local_path, remote.mtime
        end
    end
end
do_sync_from_local(relative_path, local_path, remote_path, remote, type, old) click to toggle source
# File lib/bdsync/core.rb, line 312
def do_sync_from_local relative_path, local_path, remote_path, remote, type, old
    local_changed = (File.mtime(local_path).to_i - old[:local_mtime]) != 0

    case type
    when :file
        if !remote
            if !local_changed
                local_remove_file local_path
            else
                handle_local_conflict local_path
            end
        elsif remote.directory?
            if !local_changed
                local_remove_file local_path
                local_mkdir local_path
                update_directory_data relative_path, local_path, remote.mtime
            else
                if File.mtime(local_path).to_i > remote.mtime
                    handle_remote_conflict remote_path
                    remote = upload_file local_path, remote_path
                    update_file_data relative_path, local_path, remote.mtime
                else
                    handle_local_conflict local_path
                    local_mkdir local_path
                    update_directory_data relative_path, local_path, remote.mtime
                end
            end
        else
            remote_changed = (remote.mtime - old[:remote_mtime]) != 0

            if !local_changed && !remote_changed
                @data[relative_path] = old
            elsif local_changed && !remote_changed
                remote = upload_file local_path, remote_path
                update_file_data relative_path, local_path, remote.mtime
            elsif !local_changed && remote_changed
                download_file local_path, remote_path
                update_file_data relative_path, local_path, remote.mtime
            else
                if File.mtime(local_path).to_i > remote.mtime
                    # compare file contents, conflict if not equal
                    if !is_same_contents local_path, remote_path
                        handle_remote_conflict remote_path
                        remote = upload_file local_path, remote_path
                    end

                    update_file_data relative_path, local_path, remote.mtime
                else
                    # compare file contents, conflict if not equal
                    if !is_same_contents local_path, remote_path
                        handle_local_conflict local_path
                        download_file local_path, remote_path
                    end

                    update_file_data relative_path, local_path, remote.mtime
                end
            end
        end
    when :directory
        if !remote
            if !local_changed
                local_remove_directory local_path
            else
                handle_local_conflict local_path
            end
        elsif remote.directory?
            update_directory_data relative_path, local_path, remote.mtime
        else
            if !local_changed
                local_remove_directory local_path
                download_file local_path, remote_path
                update_file_data relative_path, local_path, remote.mtime
            else
                if File.mtime(local_path).to_i > remote.mtime
                    handle_remote_conflict remote_path
                    remote = remote_mkdir remote_path
                    update_directory_data relative_path, local_path, remote.mtime
                else
                    handle_local_conflict local_path
                    download_file local_path, remote_path
                    update_file_data relative_path, local_path, remote.mtime
                end
            end
        end
    end
end
do_sync_from_remote(relative_path, local_path, remote_path, remote, type, old) click to toggle source
# File lib/bdsync/core.rb, line 225
def do_sync_from_remote relative_path, local_path, remote_path, remote, type, old
    remote_changed = (remote.mtime - old[:remote_mtime]) != 0

    case type
    when :file
        if !File.exist? local_path
            if !remote_changed
                remote_remove_file remote_path
            else
                handle_remote_conflict remote_path
            end
        elsif File.directory? local_path
            if !remote_changed
                remote_remove_file remote_path
                remote = remote_mkdir remote_path
                update_directory_data relative_path, local_path, remote.mtime
            else
                if File.mtime(local_path).to_i > remote.mtime
                    handle_remote_conflict remote_path
                    remote = remote_mkdir remote_path
                    update_directory_data relative_path, local_path, remote.mtime
                else
                    handle_local_conflict local_path
                    download_file local_path, remote_path
                    update_file_data relative_path, local_path, remote.mtime
                end
            end
        else
            local_changed = (File.mtime(local_path).to_i - old[:local_mtime]) != 0

            if !local_changed && !remote_changed
                @data[relative_path] = old
            elsif local_changed && !remote_changed
                remote = upload_file local_path, remote_path
                update_file_data relative_path, local_path, remote.mtime
            elsif !local_changed && remote_changed
                download_file local_path, remote_path
                update_file_data relative_path, local_path, remote.mtime
            else
                if File.mtime(local_path).to_i > remote.mtime
                    # compare file contents, conflict if not equal
                    if !is_same_contents local_path, remote_path
                        handle_remote_conflict remote_path
                        remote = upload_file local_path, remote_path
                    end

                    update_file_data relative_path, local_path, remote.mtime
                else
                    # compare file contents, conflict if not equal
                    if !is_same_contents local_path, remote_path
                        handle_local_conflict local_path
                        download_file local_path, remote_path
                    end

                    update_file_data relative_path, local_path, remote.mtime
                end
            end
        end
    when :directory
        if !File.exist? local_path
            if !remote_changed
                remote_remove_dir remote_path
            else
                handle_remote_conflict remote_path
            end
        elsif File.directory? local_path
            update_directory_data relative_path, local_path, remote.mtime
        else
            if !remote_changed
                remote_remove_dir remote_path
                remote = upload_file local_path, remote_path
                update_file_data relative_path, local_path, remote.mtime
            else
                if File.mtime(local_path).to_i > remote.mtime
                    handle_remote_conflict remote_path
                    remote = upload_file local_path, remote_path
                    update_file_data relative_path, local_path, remote.mtime
                else
                    handle_local_conflict local_path
                    local_mkdir local_path
                    update_directory_data relative_path, local_path, remote.mtime
                end
            end
        end
    end
end
handle_local_conflict(local_path) click to toggle source
# File lib/bdsync/core.rb, line 415
def handle_local_conflict local_path
    ts = Utils.timestamp
    local_conflict_path = local_path.sub(@local_root_path, "#{@local_root_path}/.conflict") + "." + ts

    local_ensure_parent local_conflict_path
    local_rename local_path, local_conflict_path
end
handle_local_entry(path, type) click to toggle source
# File lib/bdsync/core.rb, line 139
def handle_local_entry path, type
    puts "#{'%9s' % type}: #{path}".green

    # NOTE: force_encoding to output readable japanese text
    relative_path = path.sub(@local_root_path + "/", "").force_encoding("UTF-8")

    local_path = "#{@local_root_path}/#{relative_path}"
    remote_path = "#{@remote_root_path}/#{relative_path}"

    remote = remote_get_object remote_path

    old = @old_data[relative_path]

    if !old             # no old sync record
        do_first_time_sync_from_local relative_path, local_path, remote_path, remote, type
    else                # old sync record found
        do_sync_from_local relative_path, local_path, remote_path, remote, type, old
    end
end
handle_remote_conflict(remote_path) click to toggle source
# File lib/bdsync/core.rb, line 423
def handle_remote_conflict remote_path
    ts = Utils.timestamp
    remote_conflict_path = remote_path.sub(@remote_root_path, "#{@remote_root_path}/.conflict") + "." + ts

    remote_ensure_parent remote_conflict_path
    remote_rename remote_path, remote_conflict_path
end
handle_remote_entry(remote, path, type) click to toggle source
# File lib/bdsync/core.rb, line 121
def handle_remote_entry remote, path, type
    puts "#{'%9s' % type}: #{path}".green

    # NOTE: force_encoding to output readable japanese text
    relative_path = path.sub(@remote_root_path + "/", "").force_encoding("UTF-8")

    local_path = "#{@local_root_path}/#{relative_path}"
    remote_path = "#{@remote_root_path}/#{relative_path}"

    old = @old_data[relative_path]

    if !old             # no old sync record
        do_first_time_sync_from_remote relative_path, local_path, remote_path, remote, type
    else                # old sync record found
        do_sync_from_remote relative_path, local_path, remote_path, remote, type, old
    end
end
is_same_contents(local_path, remote_path) click to toggle source
# File lib/bdsync/core.rb, line 459
def is_same_contents local_path, remote_path
    local_file_md5 = Utils.file_md5 local_path
    remote_file_md5 = get_remote_file_md5 remote_path
    local_file_md5 == remote_file_md5
end
load_data() click to toggle source
# File lib/bdsync/core.rb, line 53
def load_data
    puts "\nload #{@data_path}"
    YAML.load_file @data_path
rescue Errno::ENOENT
    {}
end
local_ensure_dir(path) click to toggle source
# File lib/bdsync/core.rb, line 451
def local_ensure_dir path
    FileUtils.mkdir_p path if !File.directory? path
end
local_ensure_parent(path) click to toggle source
# File lib/bdsync/core.rb, line 455
def local_ensure_parent path
    local_ensure_dir File.dirname path
end
local_mkdir(local_path) click to toggle source
# File lib/bdsync/core.rb, line 431
def local_mkdir local_path
    puts "#{Utils.caller_info 1} mkdir_p #{local_path}".white
    FileUtils.mkdir_p local_path
end
local_remove_directory(local_path) click to toggle source
# File lib/bdsync/core.rb, line 441
def local_remove_directory local_path
    puts "#{Utils.caller_info 1} rm_rf #{local_path}".white
    FileUtils.rm_rf local_path
end
local_remove_file(local_path) click to toggle source
# File lib/bdsync/core.rb, line 436
def local_remove_file local_path
    puts "#{Utils.caller_info 1} rm #{local_path}".white
    FileUtils.rm local_path
end
local_rename(local_path, new_local_path) click to toggle source
# File lib/bdsync/core.rb, line 446
def local_rename local_path, new_local_path
    puts "#{Utils.caller_info 1} mv #{local_path} #{new_local_path}".yellow
    FileUtils.mv local_path, new_local_path
end
save_data(data) click to toggle source
# File lib/bdsync/core.rb, line 60
def save_data data
    local_ensure_parent @data_path
    File.write @data_path, data.to_yaml
    puts "\nsaved to #{@data_path}"
end
synchronize() click to toggle source
# File lib/bdsync/core.rb, line 23
def synchronize
    # Run only one instance of a Ruby program at the same time - self locking
    # SEE: https://code-maven.com/run-only-one-instance-of-a-script
    Utils.try_lock {
        loop {
            @old_data = load_data
            @data = {}

            remote_ensure_dir @remote_root_path
            local_ensure_dir @local_root_path

            puts "\n==== traverse_remote_path ===="
            traverse_remote_path @remote_root_path

            # merge @data to @old_data, and clear @data
            @old_data.merge! @data
            @data     = {}

            puts "\n==== traverse_local_path ===="
            traverse_local_path @local_root_path

            save_data @data

            break if !@infinite_loop

            sleep 1
        }
    }
end
traverse_local_path(local_path) click to toggle source
# File lib/bdsync/core.rb, line 95
def traverse_local_path local_path
    next_level_dirs = []

    Dir.foreach(local_path) { |entry_name|
        next if [".", "..", ".conflict"].include? entry_name

        path = "#{local_path}/#{entry_name}"

        if File.directory? path
            next_level_dirs << path
        else
            handle_local_entry path, :file
        end
    }

    next_level_dirs.sort.each { |path|
        handle_local_entry path, :directory

        begin
            traverse_local_path path
        rescue Errno::ENOENT
            # OK: the local directory may be deleted with synchronization!
        end
    }
end
traverse_remote_path(remote_path) click to toggle source
# File lib/bdsync/core.rb, line 66
def traverse_remote_path remote_path
    next_level_dirs = []

    remote_dir_foreach(remote_path) { |entry|
        next if [".", "..", ".conflict"].include? entry.name

        path = "#{remote_path}/#{entry.name}"
        remote = entry.attributes

        if remote.directory?
            next_level_dirs << [path, remote]
        else
            handle_remote_entry remote, path, :file
        end
    }

    next_level_dirs.sort.each { |path, remote|
        handle_remote_entry remote, path, :directory

        begin
            traverse_remote_path path
        rescue Net::SFTP::StatusException
            # OK: the remote directory may be deleted with synchronization!
        rescue Errno::ENOENT
            # OK: the remote directory may be deleted with synchronization!
        end
    }
end
update_directory_data(relative_path, local_path, remote_mtime) click to toggle source
# File lib/bdsync/core.rb, line 407
def update_directory_data relative_path, local_path, remote_mtime
    @data[relative_path] = {
        type: :directory,
        remote_mtime: remote_mtime,
        local_mtime: File.mtime(local_path).to_i
    }
end
update_file_data(relative_path, local_path, remote_mtime) click to toggle source
# File lib/bdsync/core.rb, line 399
def update_file_data relative_path, local_path, remote_mtime
    @data[relative_path] = {
        type: :file,
        remote_mtime: remote_mtime,
        local_mtime: File.mtime(local_path).to_i
    }
end