var _ = require('../lib/underscore'), authcommon = require('./common'), caps = require('../server/caps'), common = require('../common'), okyaku = require('../server/okyaku'), STATE = require('../server/state'); var ADDRS = STATE.dbCache.addresses; authcommon.modCache.addresses = ADDRS; var ip_key = authcommon.ip_key; function on_client_ip(ip, clients) { var addr = {key: ip_key(ip), ip: ip, count: clients.length}; // This will leak 0-count clients. // I want them to expire after a delay, really. Should reduce churn. this.send([0, common.COLLECTION_ADD, 'addrs', addr]); } function on_refresh(info) { this.send([0, common.MODEL_SET, 'adminState', info]); } function connect() { return global.redis; } function address_view(addr) { addr = _.extend({}, addr); addr.shallow = false; var clients = STATE.clientsByIP[addr.ip]; if (clients) { addr.count = clients.length; addr.country = clients[0] && clients[0].country || ''; } return addr; } okyaku.dispatcher[authcommon.FETCH_ADDRESS] = function (msg, client) { if (!caps.can_moderate(client.ident)) return false; var ip = msg[0]; if (!authcommon.is_valid_ip(ip)) return false; var key = ip_key(ip); var addr = ADDRS[key]; if (addr) { client.send([0, common.COLLECTION_ADD, 'addrs', address_view(addr)]); return true; } // Cache miss ADDRS[key] = addr = {ip, key, shallow: true}; var r = connect(); r.hgetall('ip:'+key, function (err, info) { if (err) { if (ADDRS[key] === addr) delete ADDRS[key]; return client.kotowaru(err); } if (ADDRS[key] !== addr) return; _.extend(addr, info); client.send([0, common.COLLECTION_ADD, 'addrs', address_view(addr)]); }); return true; } okyaku.dispatcher[authcommon.SET_ADDRESS_NAME] = function (msg, client) { if (!caps.can_moderate(client.ident)) return false; var ip = msg[0], name = msg[1]; if (!authcommon.is_valid_ip(ip) || typeof name != 'string') return false; var key = ip_key(ip); name = name.trim().slice(0, 30); var m = connect().multi(); if (!name) { m.hdel('ip:' + key, 'name'); m.srem('namedIPs', key); } else { m.hset('ip:' + key, 'name', name); m.sadd('namedIPs', key); } m.exec(function (err) { if (err) return client.kotowaru(err); // should observe a publication for this cache update var addr = ADDRS[key]; if (!addr) addr = ADDRS[key] = {key: key, ip: ip}; addr.name = name; var amend = {name: name}; client.send([0, common.MODEL_SET, ['addrs', key], amend]); }); return true; }; var panelListeners = 0, panelInterval = 0; function listen_panel(client) { STATE.emitter.on('change:clientsByIP', client.on_client_ip); STATE.emitter.on('refresh', client.on_refresh); panelListeners++; if (panelListeners == 1) { panelInterval = setInterval(refresh_panel_state, 10*1000); } } function unlisten_panel(client) { STATE.emitter.removeListener('change:clientsByIP',client.on_client_ip); STATE.emitter.removeListener('refresh', client.on_refresh); panelListeners--; if (panelListeners == 0) { clearInterval(panelInterval); panelInterval = 0; } } function snapshot_panel() { var addrCount = 0; for (var key in ADDRS) addrCount++; var ranges = STATE.dbCache.ranges; var banCount = ranges.bans ? ranges.bans.length : 0; return { memoryUsage: process.memoryUsage(), uptime: process.uptime(), addrs: addrCount, bans: banCount, }; } function refresh_panel_state() { STATE.emitter.emit('refresh', snapshot_panel()); } function subscribe() { if (this.on_client_ip) return false; this.on_client_ip = on_client_ip.bind(this); this.on_refresh = on_refresh.bind(this); this.unsubscribe_admin_state = unsubscribe.bind(this); this.once('close', this.unsubscribe_admin_state); listen_panel(this); var state = snapshot_panel(); state.visible = true; this.send([0, common.MODEL_SET, 'adminState', state]); var ips = []; for (var ip in STATE.clientsByIP) { var key = ip_key(ip); var a = ADDRS[key]; ips.push(a ? address_view(a) : { key: key, ip: ip, shallow: true, count: STATE.clientsByIP[ip].length }); } this.send([0, common.COLLECTION_RESET, 'addrs', ips]); return true; } function unsubscribe() { if (!this.on_client_ip) return false; unlisten_panel(this); this.removeListener('close', this.unsubscribe_admin_state); this.on_client_ip = null; this.on_refresh = null; this.unsubscribe_admin_state = null; this.send([0, common.MODEL_SET, 'adminState', {visible: false}]); return true; } okyaku.dispatcher[common.SUBSCRIBE] = function (msg, client) { if (!caps.can_administrate(client.ident)) return false; if (msg[0] != 'adminState') return false; return subscribe.call(client); }; okyaku.dispatcher[common.UNSUBSCRIBE] = function (msg, client) { if (!caps.can_administrate(client.ident)) return false; if (msg[0] != 'adminState') return false; return unsubscribe.call(client); };