#!@@MHC_RUBY_PATH@@ -Ke # -*- ruby -*- ### mhc-sync -- Data synchronization tool for MHC. ## ## Author: Yoshinari Nomura ## ## Created: 2000/04/26 ## Revised: 2000/10/30 16:43:25 ## ## mhc-sync [-n] [-x exchange_id] [-r local_dir] [user@]remote.host[:dir] ## ## -n : Do nothing effectives. Useful for checking. ## -x exchange_id : Set identical id for each pair of machines. ## -r dir : Set local repository directory of the MHC. ## (default: ~/Mail/schedule) ## ## * You must install ssh and mhc-sync in both systems. ## * Before you use it, you must make sure that the remote ## repository and the local repository are identical. ## ## for example on your note pc. ## rsync -a --delete \ ## server_host:/home/someone/Mail/schedule/ /home/someone/Mail/schedule ## (add or not add the trailing / is meaningful) ## TODO: ## Much more Error Checking... ## Repository is not unconfigurable yet. ## ## mhc-sync -s [-n] [-x exchange_id] [-r local_dir] ## -- server mode. do not invoke by hand. ## ## global settings. ## require 'mhc-kconv' require 'mhc-date' require 'mhc-schedule' $DEBUG = false STDOUT .sync = true STDIN .sync = true $SSH = 'ssh' $CMD = 'mhc-sync' $DELIMITER = "\x1\x1\x1\x1\n" $DELIMITER2 = "\x1\x1\x1\x1\x1\x1\x1\x1\n" $CONFLICT_INFO = "-- conflict --" * 5 + "\n" ## ## add some functionalities to MhcScheduleDB ## class MhcScheduleItem def info ret = format("%s (first occurred %s)", subject, occur_min .to_s1('-')) return MhcKconv::todisp(ret) end end class MhcScheduleDB def modify_sch(sch) old_sch = get_record_by_id(sch .rec_id) del_sch(old_sch, false) if old_sch add_sch(sch) end def scan_all @record_id_to_path_hash = {} Dir .glob(@basedir + '/{[0-9]*/[0-9]*,intersect}/[0-9]*') .each{|filename| file = File .open(filename, 'r') header = file .gets("\n\n") file .close if header =~ /^X-SC-Record-Id:\s*(\S+)/i @record_id_to_path_hash[$1] = filename STDERR .print "#{filename} -> #{$1}\n" if $DEBUG end } return self end def get_record_by_id(record_id) if @record_id_to_path_hash[record_id] return MhcScheduleItem .new(@record_id_to_path_hash[record_id]) else return nil end end end ## ## option check ## def usage print ' usage: mhc-sync [-n] [-x exchange_id] [-r local_dir] [user@]remote.host[:dir] -n : Do nothing effectives. Useful for checking. -x exchange_id : Set identical id for each pair of machines. -r local_dir : Set local repository directory of the MHC. (default: ~/Mail/schedule) * You must install ssh and mhc-sync in both systems. * Before you use it, you must make sure that the remote repository and the local repository are identical. ' exit 1 end $flag_noharm, $flag_syncid, $flag_serverp = false, nil, false $flag_server_user, $flag_server_host, $flag_server_dir = nil, nil, nil $flag_dir = File .expand_path("~/Mail/schedule") $flag_log_file = nil while ARGV .length > 0 case ARGV[0] when '-n' $flag_noharm = true when '-s' $flag_serverp = true when '-x' ARGV .shift $flag_syncid = ARGV[0] when '-r' ARGV .shift $flag_dir = ARGV[0] usage() if ! File .directory?($flag_dir) when /^-/ usage() else if ARGV[0] =~ /(([^@]+)@)?([^:\s]+)(:(.*))?/ $flag_server_user, $flag_server_host, $flag_server_dir = $2, $3, $5 else usage() end end ARGV .shift end $flag_log_file = $flag_dir + '/.mhc-db-log' usage() if $flag_serverp and $flag_server_host usage() if !$flag_serverp and !$flag_server_host if $DEBUG STDERR .print "Serverp = #{$flag_serverp .inspect}\n" STDERR .print "Dry run = #{$flag_noharm .inspect}\n" STDERR .print "Sync id = #{$flag_syncid .inspect}\n" STDERR .print "Remote user = #{$flag_server_user .inspect}\n" STDERR .print "Remote host = #{$flag_server_host .inspect}\n" STDERR .print "Remote repository = #{$flag_server_dir .inspect}\n" STDERR .print "Local repository = #{$flag_dir .inspect}\n" STDERR .print "Local logfile = #{$flag_log_file .inspect}\n" end ## ## ## if $flag_serverp STDERR .print "Initalizing remote (exchange_id=#{$flag_syncid}) ..." else STDERR .print "Initalizing local ..." end db = MhcScheduleDB .new($flag_dir).scan_all log = MhcLog .new($flag_log_file) log_entry = log .shrink_entries($flag_syncid) STDERR .print " done.\n" ################ # server side ################ if $flag_serverp ## server sends: ## ## M 2000-05-22 13:45:43 ... (1) ## M 2000-05-22 13:45:43 ... (2) ## DELIMITER ## contents of (1) ## DELIMITER ## contents of (2) ## : ## DELIMITER ## contents of (n) ## DELIMITER2 log_entry .each{|e| print e .to_s, "\n" } log_entry .each{|e| if e .status == 'M' or e .status == 'A' print $DELIMITER sch = db .get_record_by_id(e .rec_id) if sch print sch .dump else STDERR .print "#{e .rec_id} missing\n" end end } print $DELIMITER2 lines = '' got_delimiter = false while line = STDIN .gets if line =~ /^#{$DELIMITER2}/ got_delimiter = true break end lines += line end if got_delimiter commands = lines .split($DELIMITER) commands .each{|command| if command =~ /^DELETE\s+(\S+)/ rec_id = $1 sch = db .get_record_by_id(rec_id) if sch STDERR .print "server: deleting #{sch .info}\n" if !$flag_noharm db .del_sch(sch) else STDERR .print "S:deleting #{sch .info}\n" end end elsif command =~ /^MODIFY/ dummy, sch_string = command .split("\n", 2) sch = MhcScheduleItem .new(sch_string, false) if !$flag_noharm db .modify_sch(sch) else STDERR .print "S:modifiying #{sch .info}\n" STDERR .print "*" * 70, "\n" STDERR .print sch .dump STDERR .print "*" * 70, "\n" end end } logent = MhcLogEntry .new('S', Time .now, $flag_syncid) if !$flag_noharm log .add_entry(logent) else STDERR .print "S:adding log entry #{logent}\n" end end ################ # client side ################ else STDERR .print"Connecting #{$flag_server_host} ...\n" svr = ($flag_server_user ? $flag_server_user + '@':'') + $flag_server_host dry = $flag_noharm ? '-n' : '' xid = $flag_syncid ? "-x #{$flag_syncid}" : '' dir = $flag_server_dir ? "-r #{$flag_server_dir}" : '' STDERR .print "#{$SSH} -x #{svr} #{$CMD} -s #{dry} #{xid} #{dir}\n" if $DEBUG inout = IO .popen("#{$SSH} -x #{svr} #{$CMD} -s #{dry} #{xid} #{dir}", "r+") inout .sync = true lines = '' svr_log_entry = {} svr_sch_entry = {} cli_log_entry = {} while line = inout .gets break if line =~ /^#{$DELIMITER2}/ lines += line end log_lines, sch_lines = lines .split($DELIMITER, 2) if log_lines log_lines .split("\n") .each{|line| STDERR .print "LOGENTRY: #{line}\n" if $DEBUG ent = MhcLogEntry .new(line) svr_log_entry[ent .rec_id] = ent } end if sch_lines sch_lines .split($DELIMITER) .each{|sch_article| sch = MhcScheduleItem .new(sch_article, false) svr_sch_entry[sch .rec_id] = sch } end ################ log_entry .each{|e| cli_log_entry[e .rec_id] = e } ################################################################ # # D ... Deleted # M ... Modified or Created # - ... No entry or no change after last sync. # # Local # +---+---------------------------------+ # R | | D M - | # e +---+---------------------------------+ # m | D | -- CONFLICT L DELETE | # o | M | CONFLICT CONFLICT R->L | # t | - | R DELETE L->R -- | # e +---+---------------------------------+ # # keys = (cli_log_entry .keys + svr_log_entry .keys) .uniq keys .each{|rec_id| l_val = cli_log_entry[rec_id] r_val = svr_log_entry[rec_id] l_status, r_status = '-', '-' r_status = r_val .status if r_val l_status = l_val .status if l_val r_status = 'M' if r_status == 'A' l_status = 'M' if l_status == 'A' lr_status = l_status + r_status r_sch = svr_sch_entry[rec_id] l_sch = db .get_record_by_id(rec_id) # STDERR .print "lr_status(#{rec_id}): #{lr_status}\n" case lr_status when 'DD' # peaceful ;; when 'DM' # conflict ## add the remote article to the local and inform it. STDERR .print "---------------------------------------------------\n" STDERR .print "You modified #{rec_id} : #{r_sch .info} in remote. " STDERR .print "But you deleted it in local at the same time.\n" STDERR .print "I tranferred it from remote to local again.\n" if !$flag_noharm db .add_sch(r_sch) else STDERR .print "C:adding #{r_sch .info}\n" STDERR .print "*" * 70, "\n" STDERR .print r_sch .dump STDERR .print "*" * 70, "\n" end when 'D-' # delete remote STDERR .print "---------------------------------------------------\n" STDERR .print "Delete remote #{rec_id}\n" inout .print "DELETE #{rec_id}\n" inout .print $DELIMITER when 'MD' # conflict ## add the remote article to the local and inform it. STDERR .print "---------------------------------------------------\n" STDERR .print "You modified #{rec_id} : #{l_sch .info} in local. " STDERR .print "But you deleted it in remote at the same time.\n" STDERR .print "I preserve the local one.\n" when 'MM' # conflict ## add the remote article to the local and inform it. STDERR .print "---------------------------------------------------\n" STDERR .print "You modified #{rec_id} : \n#{l_sch .info} in local.\n" STDERR .print "But you modified it in remote at the same time.\n" STDERR .print "I concatinate the remote one to the local one.\n" STDERR .print "Also, I preserved the remote one.\n" STDERR .print "Please reedit the local one and do sync again.\n" l_sch .set_description(l_sch .description .to_s + $CONFLICT_INFO + r_sch .dump) if !$flag_noharm db .modify_sch(l_sch) else STDERR .print "C:modifiying #{l_sch .info}\n" STDERR .print "*" * 70, "\n" STDERR .print l_sch .dump STDERR .print "*" * 70, "\n" end when 'M-' # transfer local to remote STDERR .print "---------------------------------------------------\n" STDERR .print "Transfer local to remote #{l_sch .info}\n" inout .print "MODIFY\n" inout .print "#{l_sch .dump}" inout .print $DELIMITER when '-D' # delete local if l_sch STDERR .print "---------------------------------------------------\n" STDERR .print "Delete local #{l_sch .info}\n" db .del_sch(l_sch) if !$flag_noharm end when '-M' # transfer remote to local STDERR .print "---------------------------------------------------\n" STDERR .print "Transfer remote to local #{r_sch .info}\n" if !$flag_noharm db .modify_sch(r_sch) else STDERR .print "C:modifiying #{r_sch .info}\n" STDERR .print "*" * 70, "\n" STDERR .print r_sch .dump STDERR .print "*" * 70, "\n" end when '--' # peaceful ;; end } inout .print $DELIMITER2 logent = MhcLogEntry .new('S', Time .now, $flag_syncid) if !$flag_noharm log .add_entry(logent) else STDERR .print "C:adding log entry #{logent}\n" end end ### Copyright Notice: ## Copyright (C) 1999, 2000 Yoshinari Nomura. All rights reserved. ## Copyright (C) 2000 MHC developing team. All rights reserved. ## Redistribution and use in source and binary forms, with or without ## modification, are permitted provided that the following conditions ## are met: ## ## 1. Redistributions of source code must retain the above copyright ## notice, this list of conditions and the following disclaimer. ## 2. 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. ## 3. Neither the name of the team nor the names of its contributors ## may be used to endorse or promote products derived from this software ## without specific prior written permission. ## ## THIS SOFTWARE IS PROVIDED BY Yoshinari Nomura 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 ## Yoshinari Nomura 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. ### mhc-sync ends here