#!/usr/bin/ruby
# Filename:	xnewmail
# Author:	David Ljung Madison <DaveSource.com>
# See License:	http://MarginalHacks.com/License/
# Description:	Watch for mail change over an ssh remote server and start xmailer

##################################################
# Usage
##################################################
PROGNAME = File.basename($0)

## EDIT THESE VARIABLES
MAILUSER = 'dave'
MAILHOST = 'getdave.com'

## FOR STARTING YOUR MAILER
TERMINAL = "gnome-terminal --title #{PROGNAME} --geometry 80x45 --"
MAILER = 'elm'

## OPTIONAL: XDOTOOL to keep window from stealing focus when you don't expect it
## To get on apt systems:  apt-get install xdotool
XDOTOOL = '/usr/bin/xdotool'

## PROBABLY DON'T NEED TO CHANGE THESE
MAILBOXES = '/var/mail/'
SSH = (MAILHOST == 'getdave.com') ? 'dssh' : 'ssh'
SLEEPTIME = 2

PROCESS_CHECK = "pgrep -c -u #{MAILUSER} -x #{MAILER}"
# If you don't have pgrep, this works but the output is '0' for running...
#PROCESS_CHECK = "ps -aux | grep #{MAILER} | grep -v grep . 

MAILBOX = MAILBOXES+MAILUSER
MAILSSH = MAILUSER+'@'+MAILHOST
EXITFILE = "/tmp/exit.#{PROGNAME}"	# Clean exit program by creating this file

##################################################
# Focus control: xdotool
##################################################
def getFocus(opt)
	return nil unless XDOTOOL && File.exist?(XDOTOOL)
	`#{XDOTOOL} getwindowfocus`.chomp
end

def setFocus(opt, win)
	return nil unless XDOTOOL && File.exist?(XDOTOOL)
	system(XDOTOOL,'windowfocus',win)
end

def onFocusChange(opt, fromwin)
	return nil unless XDOTOOL && File.exist?(XDOTOOL)
	# Wait about two seconds for the window to show up
	40.times {
		win = getFocus(opt)
		return yield(fromwin,win) if win != fromwin
		sleep 0.05
	}
	yield(fromwin,'???')
end

##################################################
# Launch Mailer
##################################################
def mailer(opt)
	puts "Starting mailer" if opt[:d]>0
	focus = getFocus(opt)
	system("#{TERMINAL} #{SSH} -t #{MAILSSH} #{MAILER}")
	onFocusChange(opt, focus) { setFocus(opt,focus) }
end

##################################################
# Options
##################################################
def fatal(*msg)
	msg.each { |m| $stderr.puts "[#{PROGNAME}] ERROR: #{m}" }
	exit(-1);
end

def usage(*msg)
	msg.each { |m| $stderr.puts "ERROR: #{m}" }
	$stderr.puts <<-USAGE

Usage:  #{PROGNAME} [-d] <file>
  Watch for remote mail (ssh) and start up xmailer window
  -daemon   Run as daemon
  -exit     Force other #{PROGNAME} process to exit
  -d        Set debug mode
  -D        Heavy debug mode

	USAGE
	exit -1;
end

def doExit
	system('touch',EXITFILE)
	exit
end

def parseArgs
	opt = Hash.new
	opt[:d] = 0
	loop {
		if (arg=ARGV.shift)==nil then break
		elsif arg == '-h' then usage
		elsif arg == '-?' then usage
		elsif arg == '-daemon' then opt[:daemon] = 1
		elsif arg == '-d' then opt[:d] += 1
		elsif arg == '-D' then opt[:d] += 2
		elsif arg == '-exit' then doExit()
		else usage("Unknown arg [#{arg}]")
		end
	}

	opt[:d] = 0 if opt[:daemon] && Process.daemon()==0

	opt
end

##################################################
# Main code
##################################################
# Run a command on ssh that returns a number
# This is how we get around the messy handshake between sending
# a command and how many lines it may return and/or the prompt
def ssh_num_cmd(opt, ssh, cmd)
	ssh.puts cmd
	# The sleep goes here to help ensure the command has finished
	sleep SLEEPTIME
	attempts = 20
	while attempts>0
		line = ssh.gets
		return nil unless line
		return $1.to_i if line.match(/^(\d+)\s*$/)
		attempts -= 1
		sleep 1
	end
	return nil
end

def mailer_running(opt, ssh)
	running = ssh_num_cmd(opt, ssh, PROCESS_CHECK)
	running == 1
end

def main
	opt = parseArgs

	ssh = IO.popen([SSH,'-tt',MAILSSH], 'w+')
	count = 0
	lastTime = nil
	fails = 10
	while 1
		# Get mailbox time
		time = ssh_num_cmd(opt, ssh, "stat -c %Y #{MAILBOX}")
		time ? (fails = 5) : (fails -= 1)
		fatal("Failure to read remote mailbox status - exiting") if fails <= 0
		next unless time

		# Has it changed?
		if lastTime && lastTime != time
			mailer(opt) unless mailer_running(opt, ssh)
			lastTime = nil	# Don't immediately start the mailer again
		else
			lastTime = time
		end
		puts "Mailbox time: #{time}" if opt[:d]>1

		# Check for done condition
		next unless File.exist?(EXITFILE)
		puts "Exiting due to file [#{EXITFILE}]" if opt[:d]>0
		File.delete(EXITFILE)
		ssh.puts "exit\n\n"
		sleep 1
		ssh.close
		break
	end
end
main
