'use strict'; const fs = require('fs'); // We can either use a process-wide object regularly saved to a JSON file, // or a Redis equivalent (for multi-process / when the filesystem is unreliable. let redis; let useRedis = false; if (process.env.REDISTOGO_URL) { const redisToGo = require('url').parse(process.env.REDISTOGO_URL); redis = require('redis').createClient(redisToGo.port, redisToGo.hostname); redis.auth(redisToGo.auth.split(':')[1]); useRedis = true; } let analytics = {}; let autosaveIntervalId; const analyticsPath = process.env.SHIELDS_ANALYTICS_FILE || './analytics.json'; function performAutosave() { const contents = JSON.stringify(analytics); if (useRedis) { redis.set(analyticsPath, contents); } else { fs.writeFileSync(analyticsPath, contents); } } function scheduleAutosaving() { const analyticsAutoSavePeriod = 10000; autosaveIntervalId = setInterval(performAutosave, analyticsAutoSavePeriod); } // For a clean shutdown. function cancelAutosaving() { if (autosaveIntervalId) { clearInterval(autosaveIntervalId); autosaveIntervalId = null; } performAutosave(); } function defaultAnalytics() { const analytics = Object.create(null); // In case something happens on the 36th. analytics.vendorMonthly = new Array(36); resetMonthlyAnalytics(analytics.vendorMonthly); analytics.rawMonthly = new Array(36); resetMonthlyAnalytics(analytics.rawMonthly); analytics.vendorFlatMonthly = new Array(36); resetMonthlyAnalytics(analytics.vendorFlatMonthly); analytics.rawFlatMonthly = new Array(36); resetMonthlyAnalytics(analytics.rawFlatMonthly); analytics.vendorFlatSquareMonthly = new Array(36); resetMonthlyAnalytics(analytics.vendorFlatSquareMonthly); analytics.rawFlatSquareMonthly = new Array(36); resetMonthlyAnalytics(analytics.rawFlatSquareMonthly); return analytics; } function load() { const defaultAnalyticsObject = defaultAnalytics(); if (useRedis) { redis.get(analyticsPath, function(err, value) { if (err == null && value != null) { // if/try/return trick: // if error, then the rest of the function is run. try { analytics = JSON.parse(value); // Extend analytics with a new value. for (const key in defaultAnalyticsObject) { if (!(key in analytics)) { analytics[key] = defaultAnalyticsObject[key]; } } return; } catch(e) { console.error('Invalid Redis analytics, resetting.'); console.error(e); } } analytics = defaultAnalyticsObject; }); } else { // Not using Redis. try { analytics = JSON.parse(fs.readFileSync(analyticsPath)); // Extend analytics with a new value. for (const key in defaultAnalyticsObject) { if (!(key in analytics)) { analytics[key] = defaultAnalyticsObject[key]; } } } catch(e) { if (e.code !== 'ENOENT') { console.error('Invalid JSON file for analytics, resetting.'); console.error(e); } analytics = defaultAnalyticsObject; } } } let lastDay = (new Date()).getDate(); function resetMonthlyAnalytics(monthlyAnalytics) { for (let i = 0; i < monthlyAnalytics.length; i++) { monthlyAnalytics[i] = 0; } } function incrMonthlyAnalytics(monthlyAnalytics) { try { const currentDay = (new Date()).getDate(); // If we changed month, reset empty days. while (lastDay !== currentDay) { // Assumption: at least a hit a month. lastDay = (lastDay + 1) % monthlyAnalytics.length; monthlyAnalytics[lastDay] = 0; } monthlyAnalytics[currentDay]++; } catch(e) { console.error(e.stack); } } function noteRequest(queryParams, match) { incrMonthlyAnalytics(analytics.vendorMonthly); if (queryParams.style === 'flat') { incrMonthlyAnalytics(analytics.vendorFlatMonthly); } else if (queryParams.style === 'flat-square') { incrMonthlyAnalytics(analytics.vendorFlatSquareMonthly); } } function setRoutes (server) { server.ajax.on('analytics/v1', (json, end) => { end(analytics); }); } module.exports = { load, scheduleAutosaving, cancelAutosaving, noteRequest, setRoutes };