#! /usr/bin/env lua5.1
-- 
-- Released under the terms of GPLv3 or at your option any later version.
-- No warranties.
-- Copyright Enrico Tassi <gares@fettunta.org>

require 'syncmaildir'

-- export to the global namespace all symbols
for k,v in pairs(syncmaildir) do _G[k] = v end

-- ============================= MAIN =====================================

function main()
	-- sanity checks
	assert_exists(MDDIFF)
	assert_exists(XDELTA)

	-- argument parsing
	local exclude = {}
	local no_delete = false
	local just_mddiff = false
	local stop_after_diff = false
	local override_db = nil
	local dump_stdin_to = nil
	local usage = "Usage: "..arg[0]:match('[^/]+$')..
		" [-vdn] endpointname mailboxes...\n"
	while #arg > 2 do
		if arg[1] == '-v' or arg[1] == '--verbose' then
			set_verbose(true)
			table.remove(arg,1)
		elseif arg[1] == '-d' or arg[1] == '--dry-run' then
			set_dry_run(true)
			table.remove(arg,1)
		elseif arg[1] == '-n' or arg[1] == '--no-delete' then
			no_delete = true
			table.remove(arg,1)
		elseif arg[1] == '--exclude' then
			exclude[#exclude + 1] = arg[1]
			exclude[#exclude + 1] = arg[2]
			table.remove(arg,1)
			table.remove(arg,1)
		elseif arg[1] == '--get-mddiff-cmdline' then
			just_mddiff = true
			table.remove(arg,1)
		elseif arg[1] == '--override-db' then
			override_db = arg[2]
			table.remove(arg,1)
			table.remove(arg,1)
		elseif arg[1] == '--stop-after-diff' then
			stop_after_diff = true
			table.remove(arg,1)
		elseif arg[1] == '--dump-stdin' then
			dump_stdin_to = arg[2]
			table.remove(arg,1)
			table.remove(arg,1)
		else
			break
		end
	end
	
	if #arg < 2 then
		io.stderr:write(usage)
		os.exit(2)
	end

	if dump_stdin_to ~= nil then
		dump_stdin_to = dump_stdin_to:gsub('^~',os.getenv('HOME'))
		mkdir_p(dump_stdin_to)
		local f = io.open(dump_stdin_to,'w')
		local data = nil
		repeat
				data = io.read(4096)
				if data then f:write(data) end
		until data == nil
		f:close()
		os.exit(0)
	end

	local endpoint = arg[1]
	table.remove(arg,1)
	local dbfile = nil
	if override_db ~= nil then
		dbfile = override_db:gsub('^~',os.getenv('HOME'))
	else
		dbfile = dbfile_name(endpoint, arg)
	end
	local xdelta = dbfile .. '.xdelta'
	local dbfilemt = dbfile .. '.mtime'
	local newdb = dbfile .. '.new'
	local newdbmt = dbfilemt .. '.new'

	local database_opt = '--db-file '.. dbfile
	local mailbox_opt = table.concat(arg,' ')
	local no_delete_opt = ''
	if no_delete then no_delete_opt = '-n' end
	local dry_opt = ''
	if dry_run() then dry_opt = '-d' end
	local exclude_opt = table.concat(exclude, ' ')
	local mddiff = MDDIFF..' '..dry_opt..' '..database_opt..' '..
		exclude_opt..' '..no_delete_opt..' '..mailbox_opt
	
	-- to call mddiff from another tool with the same cmdline
	if just_mddiff then
		print(mddiff)
		os.exit(0)
	end


	-- we check the protocol version and dbfile fingerprint
	handshake(dbfile)
	
	-- run mddiff and send the output to the client
	local r = io.popen(mddiff,"r")
	local sent = 0
	while true do
		local l = r:read("*l")
		if l ~= nil then
			sent = sent + 1
			io.write(l,'\n')
		else
			break
		end
	end
	r:close()
	
	-- end of the first phase, now the client should
	-- apply the diff eventually asking for the transmission
	-- of some data
	io.write('END\n')
	io.flush()

	-- if renaming mode, we exit
	if stop_after_diff then
		if override_db ~= nil then
			os.remove(dbfile)
			os.remove(dbfilemt)
			os.remove(newdb)
			os.remove(newdbmt)
		end
		os.exit(0)
	end
	
	-- process client commands
	while true do
		local l = io.read('*l')
		if l == nil then 
			-- end of input stream, client is dead
			log_error('Communication with client died unexpectedly\n')
			os.exit(3)
		end
		if l:match('^COMMIT$') then
			-- the client applied the diff, the new mailbox
			-- fingerprint should be used for the next sync
			local rc
			if not dry_run() then
				rc = os.execute(
					XDELTA..' delta '..dbfile..' '..newdb..' '..xdelta)
			else
				local f = io.open(xdelta,'w')
				f:close()
				rc = 0 -- there is no newdb if --dry-run is given
			end
			if rc ~= 0 and rc ~= 256 then
				log_error('Failed running `xdelta delta` on db file: '..rc)
				os.exit(4)
			end
			transmit(io.stdout, xdelta, "all")
			os.remove(xdelta)
		elseif l:match('^DONE$') then
			if not dry_run() then os.rename(newdb, dbfile) end 
			if not dry_run() then os.rename(newdbmt, dbfilemt) end 
			os.exit(0)
		elseif l:match('^ABORT$') then
			-- the client failed in applying the diff
			log_error('Client aborted, removing '..
				newdb..' and '..newdbmt..'\n')
			if not dry_run() then os.remove(newdb) end
			if not dry_run() then os.remove(newdbmt) end
			os.exit(5)
		elseif l:match('^GET ') then
			local path = parse(l, '^GET (%S.*)$')
			transmit(io.stdout, path, "all")
		elseif l:match('^GETHEADER ') then
			local path = parse(l, '^GETHEADER (%S.*)$')
			transmit(io.stdout, path, "header")
		elseif l:match('^GETBODY ') then
			local path = parse(l, '^GETBODY (%S.*)$')
			transmit(io.stdout, path, "body")
		else
			-- protocol error
			log_error('Invalid command '..l..'\n')
			os.exit(6)
		end
	end
end

-- no more globals
set_strict()

-- parachute for error
parachute(main, 7)

-- vim:set ts=4:
