#!/usr/bin/ruby

require 'xmlrpc/server'
require 'dbi'
require 'digest/sha1'
require 'yaml'
require 'socket'
require 'timeout'

ERROR_UNKNOWN=1
ERROR_DB=2
ERROR_PORT=3
ERROR_METHOD_MISSING=-99

yml = YAML.load_file('../genkidama_stats/config.yml')

s = XMLRPC::CGIServer.new

s.add_handler('version.check') do ||
    {:required_version => yml['required_version'], 
    :newest_version => yml['newest_version'],
    :version_up_url => yml['version_up_url']}
end

s.add_handler('tcpPort.check') do |port|
  begin
    timeout(yml['connection_timeout']) do
      s = TCPSocket.open(ENV['REMOTE_ADDR'], port)
      s.close
    end
    true
  rescue TimeoutError => e
    raise XMLRPC::FaultException.new(ERROR_PORT,
                                     'Connection timeout')
  rescue SystemCallError => e
    raise XMLRPC::FaultException.new(ERROR_PORT,
                                     e.to_s)
  rescue => e
    raise XMLRPC::FaultException.new(ERROR_UNKNOWN,
                                     e.class.to_s + ' ' + e.to_s)
  end
end

s.add_handler('onlineUsers.fetch') do |port, version|
  begin
    dbh = DBI.connect("DBI:Mysql:#{yml['db_name']}:#{yml['db_host']}", yml['db_user'], yml['db_pass'])
    
    sth = dbh.execute('SELECT COUNT(*) FROM online_users WHERE address = ? AND port = ?',
                      ENV['REMOTE_ADDR'], port)
    cnt = 0
    sth.fetch do |row|
      cnt = row[0]
    end
    if cnt > 0
      dbh.do('DELETE FROM online_users WHERE address = ? AND port = ?',
             ENV['REMOTE_ADDR'], port)
      dbh.do("INSERT INTO user_log (datetime, hash, version, status) VALUES(NOW(), ?, ?, 'purge')",
             Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), version)
    end
    
    sth = dbh.execute('SELECT address, port, version FROM online_users ORDER BY id DESC LIMIT 100')
    ret = []
    sth.fetch do |row|
      ret.push({:address => row[0], :port => row[1], :version => row[2]})
    end
    sth.finish
    ret
  rescue DBI::DatabaseError => e
    raise XMLRPC::FaultException.new(ERROR_DB,
                                     e.to_s)
  rescue => e
    raise XMLRPC::FaultException.new(ERROR_UNKNOWN,
                                     e.class.to_s + ' ' + e.to_s)
  ensure
    dbh.disconnect if dbh
  end
end

s.add_handler('onlineUsers.register') do |port, version|
  begin
    timeout(yml['connection_timeout']) do
      s = TCPSocket.open(ENV['REMOTE_ADDR'], port)
      s.close
    end
    dbh = DBI.connect("DBI:Mysql:#{yml['db_name']}:#{yml['db_host']}", yml['db_user'], yml['db_pass'])
    sth = dbh.execute('SELECT COUNT(*) FROM online_users WHERE address = ? AND port = ?',
                      ENV['REMOTE_ADDR'], port)
    cnt = 0
    sth.fetch do |row|
      cnt = row[0]
    end
    if cnt > 0
      dbh.do('DELETE FROM online_users WHERE address = ? AND port = ?',
             ENV['REMOTE_ADDR'], port)
      dbh.do("INSERT INTO user_log (datetime, hash, version, status) VALUES(NOW(), ?, ?, 'purge')",
             Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), version)
    end
    dbh.do('INSERT INTO online_users (datetime, address, port, version) VALUES (NOW(), ?, ?, ?)',
           ENV['REMOTE_ADDR'], port, version)
    dbh.do("INSERT INTO user_log (datetime, hash, version, status) VALUES (NOW(), ?, ?, 'login')",
           Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), version)
  
  rescue TimeoutError => e
    raise XMLRPC::FaultException.new(ERROR_PORT,
                                     'Connection timeout')
  rescue SystemCallError => e
    raise XMLRPC::FaultException.new(ERROR_PORT,
                                     e.to_s)
  rescue DBI::DatabaseError => e
    raise XMLRPC::FaultException.new(ERROR_DB,
                                     e.to_s)
  rescue => e
    raise XMLRPC::FaultException.new(ERROR_UNKNOWN,
                                     e.class.to_s + ' ' + e.to_s)
  ensure
    dbh.disconnect if dbh
  end
end

s.add_handler('onlineUsers.unregister') do |port, version|
  begin
    dbh = DBI.connect("DBI:Mysql:#{yml['db_name']}:#{yml['db_host']}", yml['db_user'], yml['db_pass'])
    dbh.do('DELETE FROM online_users WHERE address = ? AND port = ?',
           ENV['REMOTE_ADDR'], port)
    dbh.do("INSERT INTO user_log (datetime, hash, version, status) VALUES (NOW(), ?, ?, 'logout')",
           Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), version)

  rescue DBI::DatabaseError => e
    raise XMLRPC::FaultException.new(ERROR_DB,
                                     e.to_s)
  rescue => e
    raise XMLRPC::FaultException.new(ERROR_UNKNOWN,
                                     e.class.to_s + ' ' + e.to_s)
  ensure
    dbh.disconnect if dbh
  end
end

s.add_handler('onlineData.register') do |port, version, key|
  begin
    dbh = DBI.connect("DBI:Mysql:#{yml['db_name']}:#{yml['db_host']}", yml['db_user'], yml['db_pass'])
    sth = dbh.execute('SELECT COUNT(*) FROM online_data WHERE `hash` = ? AND `key` = ?',
                      Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), key)
    cnt = 0
    sth.fetch do |row|
      cnt = row[0]
    end
    if cnt > 0
      dbh.do('DELETE FROM online_data WHERE `hash` = ? AND `key` = ?',
             Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), key)
      dbh.do("INSERT INTO data_log (`datetime`, `hash`, `version`, `key`, `status`) VALUES (NOW(), ?, ?, ?, 'purge')",
             Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), version, key)
    end
    dbh.do("INSERT INTO online_data (`datetime`, `hash`, `version`, `key`) VALUES (NOW(), ?, ?, ?)",
           Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), version, key)
    dbh.do("INSERT INTO data_log (`datetime`, `hash`, `version`, `key`, `status`) VALUES (NOW(), ?, ?, ?, 'add')",
           Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), version, key)

  rescue DBI::DatabaseError => e
    raise XMLRPC::FaultException.new(ERROR_DB,
                                     e.to_s)
  rescue => e
    raise XMLRPC::FaultException.new(ERROR_UNKNOWN,
                                     e.class.to_s + ' ' + e.to_s)
  ensure
    dbh.disconnect if dbh
  end
end

s.add_handler('onlineData.unregister') do |port, version, key|
  begin
    dbh = DBI.connect("DBI:Mysql:#{yml['db_name']}:#{yml['db_host']}", yml['db_user'], yml['db_pass'])
    dbh.do('DELETE FROM online_data WHERE `hash` = ? AND `key` = ?',
           Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), key)
    dbh.do("INSERT INTO data_log (`datetime`, `hash`, `version`, `key`, `status`) VALUES (NOW(), ?, ?, ?, 'remove')",
           Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), version, key)
    
  rescue DBI::DatabaseError => e
    raise XMLRPC::FaultException.new(ERROR_DB,
                                     e.to_s)
  rescue => e
    raise XMLRPC::FaultException.new(ERROR_UNKNOWN,
                                     e.class.to_s + ' ' + e.to_s)
  ensure
    dbh.disconnect if dbh
  end
end

s.add_handler('globalIp.probe') do ||
    begin
      ENV['REMOTE_ADDR']
    rescue => e
      raise XMLRPC::FaultException.new(ERROR_UNKNOWN,
                                       e.class.to_s + ' ' + e.to_s)
    end
end

s.add_handler('nicoVideoAccessLog.append') do |port, version, video_id, access_mode, cache_url|
  begin
    dbh = DBI.connect("DBI:Mysql:#{yml['db_name']}:#{yml['db_host']}", yml['db_user'], yml['db_pass'])
    cache_url_hash = nil
    if access_mode == 'dht'
      cache_url_hash = Digest::SHA1.hexdigest(cache_url)
    end
    dbh.do('INSERT INTO nico_video_access_log (datetime, node_hash, version, video_id, access_mode, cache_url_hash) VALUES (NOW(), ?, ?, ?, ?, ?)',
           Digest::SHA1.hexdigest("#{ENV['REMOTE_ADDR']}:#{port}"), version, video_id, access_mode, cache_url_hash)
    
  rescue DBI::DatabaseError => e
    raise XMLRPC::FaultException.new(ERROR_DB,
                                     e.to_s)
  rescue => e
    raise XMLRPC::FaultException.new(ERROR_UNKNOWN,
                                     e.class.to_s + ' ' + e.to_s)
  ensure
    dbh.disconnect if dbh
  end
end

s.set_default_handler do |name, *args|
  raise XMLRPC::FaultException.new(ERROR_METHOD_MISSING,
                                   "Method #{name} missing or wrong number of parameters!")
end

s.serve
