#!/usr/bin/python

from optparse import OptionParser
from subprocess import Popen,PIPE
from sqlmix import Db
import sys,os,re,time

try:
	from cPickle import load,dump
except ImportError:
	from Pickle import load,dump

parser = OptionParser(conflict_handler="resolve", usage="%prog [ opts ] database -- pulls a mysql binlog")
parser.add_option("--help","-?", action="help",
                        help="gibt diesen Hilfstext aus")
parser.add_option("--ld", "--localdb", dest="localdb", default=None,
                        help="Database to push to")
parser.add_option("--lh", "--localhost", dest="localhost", default=None,
                        help="Hostname to push to")
parser.add_option("--lu", "--localuser", dest="localuser", default=None,
                        help="Username to log in as")
parser.add_option("--lp", "--localpass", dest="localpw", default=None,
                        help="Password to log in with")
parser.add_option("-h", "--host", dest="host", default=None,
                        help="Hostname to pull from")
parser.add_option("-u", "--user", dest="user", default=None,
                        help="Username to log in as")
parser.add_option("-p", "--pass", dest="pw", default=None,
                        help="Password to log in with")
parser.add_option("--log-name", dest="log_name", default=None,
                        help="First log file to read")
parser.add_option("--log-pos", dest="log_pos", default=None,
                        help="File position to start from")
parser.add_option("--single", dest="save", default=True, action="store_false",
                        help="Do a single run, don't save state")
parser.add_option("--verbose", dest="verbose", default=False,action="store_true",
                        help="Be verbose.")
parser.add_option("-f","--forward", dest="forward", default=False,action="store_true",
                        help="log changes (for slave updates, default OFF)")
parser.add_option("--dump", dest="dump", default=False,action="store_true",
                        help="Dump data to less instead of saving it")
parser.add_option("--fake", dest="fake", default=False,action="store_true",
                        help="initialize the metadata without copying")
parser.add_option("-l","--loop", dest="loop", default=None,action="store",type="int",
                        help="Repeat every N seconds")

(opts, args) = parser.parse_args()
if len(args) != 1 or (opts.loop and opts.fake):
        print >>sys.stderr,parser.usage
        sys.exit(1)
opts.db = args[0]

class _Opts(object): pass

try:
	opt = load(open("/var/lib/dbmirror/"+opts.db,"r"))
except IOError:
	opt = _Opts()
	new_run=True
else:
	new_run=False

for o in ("db","host","user","pw","log_name","log_pos","localuser","localpw","localdb","localhost","forward"):
	if getattr(opts,o) is not None:
		setattr(opt,o,getattr(opts,o))

if opts.save and not new_run:
	dump(opt, open("/var/lib/dbmirror/"+opt.db+".new","w"))
	os.rename("/var/lib/dbmirror/"+opt.db+".new","/var/lib/dbmirror/"+opt.db)

# Here we do stuff

sql=Db.Db(host=opt.host, username=opt.user, password=opt.pw, database=opt.db)
if opts.fake or opts.dump:
	opts.save=False

if opts.fake:
	d = sql.DoFn("show master status", _dict=1)
		
	end_name=d["File"]
	end_pos=d["Position"]

	if opts.verbose:
		print "We dump",end_name,"until",end_pos
else:
	if opts.dump:
		loc=Popen(("less"),stdin=PIPE)
	else:
		args=["mysql","--default-character-set=utf8","-f"]
		if getattr(opt,"localhost",None):
			args.append("-h"+opt.localhost)
		if getattr(opt,"localuser",None):
			args.append("-u"+opt.localuser)
		if getattr(opt,"localpw",None):
			args.append("-p"+opt.localpw)
		args.append(getattr(opt,"localdb", opt.db))
		loc=Popen(args, stdin=PIPE)
	if not opt.forward:
		print >>loc.stdin,"set sql_log_bin=0;"

	while True:
		if new_run:
			run=["mysqldump", "-h"+opt.host, "-u"+opt.user, "-p"+opt.pw, "-q","-c","--single-transaction","--master-data=2","--default-character-set=utf8","-B",opt.db]
		else:
			logs=[]
			for d in sql.DoSelect("show master logs"):
				d = d[0]
				if logs or d == opt.log_name:
					logs.append(d)
			d = sql.DoFn("show master status", _dict=1)
			
			end_name=d["File"]
			end_pos=d["Position"]

			if opts.verbose:
				print "We want",len(logs),"logs: from",opt.log_name,opt.log_pos,"to",end_name,end_pos
			if opt.log_name == end_name and opt.log_pos == end_pos:
				if opts.verbose:
					print "Nothing to do."
				if not opts.loop:
					sys.exit(0)
				time.sleep(opts.loop)

			run=["mysqlbinlog", "-R", "-h"+opt.host, "-u"+opt.user, "-p"+opt.pw, "-d",opt.db,"--start-position="+str(opt.log_pos), "--stop-position="+str(end_pos)]
			run.extend(logs)

		# WORK
		K=1024
		rem=Popen(run, stdout=PIPE)
		f=rem.stdout.read(8*K)
		if new_run:
			mm=re.compile("^-- CHANGE MASTER TO MASTER_LOG_FILE='([^']+)', MASTER_LOG_POS=(\d+);",re.M)
			#-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.006725', MASTER_LOG_POS=361345;
			while len(f) < 8*K: # It's a network conn, so short reads ?
				ff = rem.stdout.read(8192-len(f))
				if ff == "":
					break
				f += ff
			if len(f) == 8*K:
				m=mm.search(f)
				if not m:
					print >>sys.stdout,"CHANGE MASTER not found!"
					sys.exit(1)
				end_name = m.group(1)
				end_pos = int(m.group(2))
				if opts.verbose:
					print "We dump",end_name,"until",end_pos
				
		blocks=len(f)
		while f != "":
			loc.stdin.write(f)
			if opts.verbose:
				sys.stdout.write(" %d \r" % int((blocks/K)))
				sys.stdout.flush()
			f=rem.stdout.read(16*K)
			blocks += len(f)
		if opts.verbose:
			print ";"
		rem.stdout.close()
		rx=rem.wait()
		if rx:
			print >>sys.stderr, "remote mysql dumper died:",rx
			sys.exit(2)
		new_run=False
		opt.log_name=end_name
		opt.log_pos=end_pos
		if not opts.loop:
			break
		else:
			loc.stdin.flush()
			if opts.verbose:
				sys.stdout.write("\n")
				sys.stdout.flush()
			time.sleep(opts.loop)

	loc.stdin.close()
	rx=loc.wait()
	if rx:
		print >>sys.stderr, "local mysql died:",rx
		sys.exit(1)


if opts.save:
	dump(opt, open("/var/lib/dbmirror/"+opt.db+".new","w"))
	os.rename("/var/lib/dbmirror/"+opt.db+".new","/var/lib/dbmirror/"+opt.db)
