class Dbox::Syncer::Push
Public Class Methods
new(database, api)
click to toggle source
Calls superclass method
Dbox::Syncer::Operation::new
# File lib/dbox/syncer.rb, line 419 def initialize(database, api) super(database, api) end
Public Instance Methods
calculate_changes(dir)
click to toggle source
# File lib/dbox/syncer.rb, line 520 def calculate_changes(dir) raise(ArgumentError, "Not a directory: #{dir.inspect}") unless dir[:is_dir] out = [] recur_dirs = [] existing_entries = current_dir_entries_as_hash(dir) child_paths = list_contents(dir).sort child_paths.each do |p| local_path = relative_to_local_path(p) remote_path = relative_to_remote_path(p) c = { :path => p, :local_path => local_path, :remote_path => remote_path, :modified => mtime(local_path), :is_dir => is_dir(local_path), :parent_path => dir[:path], :local_hash => calculate_hash(local_path) } if entry = existing_entries[p] c[:id] = entry[:id] recur_dirs << c if c[:is_dir] # queue dir for later out << [:update, c] if modified?(entry, c) # update iff modified else # create out << [:create, c] recur_dirs << c if c[:is_dir] end end # add any deletions out += case_insensitive_difference(existing_entries.keys, child_paths).map do |p| [:delete, existing_entries[p]] end # recursively process new & existing subdirectories recur_dirs.each do |dir| out += calculate_changes(dir) end out end
create_dir(dir)
click to toggle source
# File lib/dbox/syncer.rb, line 593 def create_dir(dir) remote_path = dir[:remote_path] log.info "Creating #{remote_path}" api.create_dir(remote_path) end
delete_dir(dir)
click to toggle source
# File lib/dbox/syncer.rb, line 599 def delete_dir(dir) remote_path = dir[:remote_path] api.delete_dir(remote_path) end
delete_file(file)
click to toggle source
# File lib/dbox/syncer.rb, line 604 def delete_file(file) remote_path = file[:remote_path] api.delete_file(remote_path) end
execute()
click to toggle source
# File lib/dbox/syncer.rb, line 429 def execute dir = database.root_dir changes = calculate_changes(dir) log.debug "Executing changes:\n" + changes.map {|c| c.inspect }.join("\n") changelist = { :created => [], :deleted => [], :updated => [], :failed => [] } changes.each do |op, c| case op when :create c[:parent_id] ||= lookup_id_by_path(c[:parent_path]) if c[:is_dir] # create the remote directiory create_dir(c) database.add_entry(c[:path], true, c[:parent_id], nil, nil, nil, nil) force_metadata_update_from_server(c) changelist[:created] << c[:path] else # upload a new file begin local_hash = calculate_hash(c[:local_path]) res = upload_file(c) database.add_entry(c[:path], false, c[:parent_id], nil, nil, nil, local_hash) if case_insensitive_equal(c[:path], res[:path]) force_metadata_update_from_server(c) changelist[:created] << c[:path] else log.warn "#{c[:path]} had a conflict and was renamed to #{res[:path]} on the server" changelist[:conflicts] ||= [] changelist[:conflicts] << { :original => c[:path], :renamed => res[:path] } end rescue => e log.error "Error while uploading #{c[:path]}: #{e.inspect}\n#{e.backtrace.join("\n")}" changelist[:failed] << { :operation => :create, :path => c[:path], :error => e } end end when :update existing = database.find_by_path(c[:path]) unless existing[:is_dir] == c[:is_dir] raise(RuntimeError, "Mode on #{c[:path]} changed between file and dir -- not supported yet") end # only update files -- nothing to do to update a dir if !c[:is_dir] # upload changes to a file begin local_hash = calculate_hash(c[:local_path]) res = upload_file(c) database.update_entry_by_path(c[:path], :local_hash => local_hash) if case_insensitive_equal(c[:path], res[:path]) force_metadata_update_from_server(c) changelist[:updated] << c[:path] else log.warn "#{c[:path]} had a conflict and was renamed to #{res[:path]} on the server" changelist[:conflicts] ||= [] changelist[:conflicts] << { :original => c[:path], :renamed => res[:path] } end rescue => e log.error "Error while uploading #{c[:path]}: #{e.inspect}\n#{e.backtrace.join("\n")}" changelist[:failed] << { :operation => :update, :path => c[:path], :error => e } end end when :delete # delete a remote file/directory begin begin if c[:is_dir] delete_dir(c) else delete_file(c) end rescue Dbox::RemoteMissing # safe to delete even if remote is already gone end database.delete_entry_by_path(c[:path]) changelist[:deleted] << c[:path] rescue => e log.error "Error while deleting #{c[:path]}: #{e.inspect}\n#{e.backtrace.join("\n")}" changelist[:failed] << { :operation => :delete, :path => c[:path], :error => e } end when :failed changelist[:failed] << { :operation => c[:operation], :path => c[:path], :error => c[:error] } else raise(RuntimeError, "Unknown operation type: #{op}") end end # sort & return output sort_changelist(changelist) end
force_metadata_update_from_server(entry)
click to toggle source
# File lib/dbox/syncer.rb, line 618 def force_metadata_update_from_server(entry) res = gather_remote_info(entry) unless res == :not_modified database.update_entry_by_path(entry[:path], :modified => res[:modified], :revision => res[:revision], :remote_hash => res[:remote_hash]) end update_file_timestamp(database.find_by_path(entry[:path])) end
is_dir(path)
click to toggle source
# File lib/dbox/syncer.rb, line 569 def is_dir(path) File.directory?(path) end
list_contents(dir)
click to toggle source
# File lib/dbox/syncer.rb, line 587 def list_contents(dir) local_path = dir[:local_path] paths = Dir.entries(local_path).reject {|s| s == "." || s == ".." || s.start_with?(".") } paths.map {|p| local_to_relative_path(File.join(local_path, p)) } end
modified?(entry, res)
click to toggle source
# File lib/dbox/syncer.rb, line 573 def modified?(entry, res) out = true if entry[:is_dir] out = !times_equal?(entry[:modified], res[:modified]) log.debug "#{entry[:path]} modified? t#{time_to_s(entry[:modified])} vs. t#{time_to_s(res[:modified])} => #{out}" else eh = entry[:local_hash] rh = res[:local_hash] out = !(eh && rh && eh == rh) log.debug "#{entry[:path]} modified? #{eh} vs. #{rh} => #{out}" end out end
mtime(path)
click to toggle source
# File lib/dbox/syncer.rb, line 565 def mtime(path) File.mtime(path) end
practice()
click to toggle source
# File lib/dbox/syncer.rb, line 423 def practice dir = database.root_dir changes = calculate_changes(dir) log.debug "Changes that would be executed:\n" + changes.map {|c| c.inspect }.join("\n") end
upload_file(file)
click to toggle source
# File lib/dbox/syncer.rb, line 609 def upload_file(file) local_path = file[:local_path] remote_path = file[:remote_path] db_entry = database.find_by_path(file[:path]) last_revision = db_entry ? db_entry[:revision] : nil res = api.put_file(remote_path, local_path, last_revision) process_basic_remote_props(res) end