#!/usr/bin/ruby

# process-consensus - read a current consensus document, inserting the
#                     information into a database then calling
#                     update-named-status.rb to update the name-binding
#                     flags
#
# Copyright (c) 2007 Peter Palfrader
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

require "yaml"

require 'db'
require 'db-config'
require 'update-named-status'

$db = Db.new($CONFIG['database']['dbhost'], $CONFIG['database']['dbname'], $CONFIG['database']['user'], $CONFIG['database']['password'])

$router_cache = {}
$nickname_cache = {}

def parse_consensus consensus
	ts = nil
	routers = []
	consensus.each do |line|
		(key, value) = line.split(' ',2)
		case key
			when "valid-after", "published": ts = DateTime.parse(value)
			when "r":
				(nick, fpr, _) = value.split(' ', 3)
				nick.downcase!
				next if nick == 'unnamed'
				routers << {
					'nick' => nick,
					'fingerprint' => (fpr+'=').unpack('m').first.unpack('H*').first
					}
		end
	end
	throw "Did not find a timestamp" unless ts
	throw "Did not find any routers" unless routers.size > 0
	return ts, routers
end

def insert_routers_into_db(router, table, field, value)
	pk = table+'_id'
	row = $db.query_row("SELECT #{pk} FROM #{table} WHERE #{field}=?", value)
	if row
		return row[pk]
	else
		r = { field => value }
		$db.insert_row( table, r )
		return r[pk]
	end
end

def handle_one_consensus(c)
	puts "parsing..." if $verbose
	timestamp, routers = parse_consensus c
	puts "storing..." if $verbose

	routers.each do |router|
		fpr = router['fingerprint']
		nick = router['nick']
		$router_cache[fpr] = router_id = ($router_cache[fpr] or insert_routers_into_db(router, 'router', 'fingerprint', router['fingerprint']))
		$nickname_cache[nick] = nickname_id = ($nickname_cache[nick] or insert_routers_into_db(router, 'nickname', 'nick', router['nick']))

		row = $db.update(
			'router_claims_nickname',
			{ 'last_seen' => timestamp.to_s },
			{ 'router_id' => router_id, 'nickname_id' => nickname_id} )
		case row
			when 0:
				$db.insert('router_claims_nickname',
					{
						'first_seen' => timestamp.to_s,
						'last_seen' => timestamp.to_s,
						'router_id' => router_id, 'nickname_id' => nickname_id} )
			when 1:
			else
				throw "Update of router_claims_nickname returned unexpected number of affected rows(#{row})"
		end
	end
end

$db.transaction_begin
if ARGV.first == '-v'
	$verbose = true
	ARGV.shift
end

if ARGV.size == 0
	handle_one_consensus STDIN.readlines
	do_update $verbose
else
	ARGV.each do |filename|
		puts filename if $verbose
		handle_one_consensus File.new(filename).readlines
		puts "updating..." if $verbose
		do_update $verbose
	end
end
$db.transaction_commit
