package cc.blynk.server.core.dao; import cc.blynk.server.core.model.auth.User; import cc.blynk.server.core.model.enums.GraphType; import cc.blynk.server.core.model.enums.PinType; import cc.blynk.server.core.protocol.exceptions.NoDataException; import cc.blynk.server.core.reporting.GraphPinRequest; import cc.blynk.server.core.reporting.average.AverageAggregatorProcessor; import cc.blynk.server.core.reporting.raw.RawDataProcessor; import cc.blynk.utils.FileUtils; import cc.blynk.utils.NumberUtil; import cc.blynk.utils.ServerProperties; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import static cc.blynk.utils.ArrayUtil.EMPTY_BYTES; import static cc.blynk.utils.StringUtils.DEVICE_SEPARATOR; /** * The Blynk Project. * Created by Dmitriy Dumanskiy. * Created on 2/18/2015. */ public class ReportingDao implements Closeable { private static final Logger log = LogManager.getLogger(ReportingDao.class); public final AverageAggregatorProcessor averageAggregator; public final RawDataProcessor rawDataProcessor; public final CSVGenerator csvGenerator; public final String dataFolder; private final boolean ENABLE_RAW_DB_DATA_STORE; //for test only public ReportingDao(String reportingFolder, AverageAggregatorProcessor averageAggregator, ServerProperties serverProperties) { this.averageAggregator = averageAggregator; this.dataFolder = reportingFolder; this.ENABLE_RAW_DB_DATA_STORE = serverProperties.getBoolProperty("enable.raw.db.data.store"); this.rawDataProcessor = new RawDataProcessor(ENABLE_RAW_DB_DATA_STORE); this.csvGenerator = new CSVGenerator(this); } public ReportingDao(String reportingFolder , ServerProperties serverProperties) { this.averageAggregator = new AverageAggregatorProcessor(reportingFolder); this.dataFolder = reportingFolder; this.ENABLE_RAW_DB_DATA_STORE = serverProperties.getBoolProperty("enable.raw.db.data.store"); this.rawDataProcessor = new RawDataProcessor(ENABLE_RAW_DB_DATA_STORE); this.csvGenerator = new CSVGenerator(this); } public static String generateFilename(int dashId, int deviceId, char pinType, byte pin, GraphType type) { switch (type) { case MINUTE : return formatMinute(dashId, deviceId, pinType, pin); case HOURLY : return formatHour(dashId, deviceId, pinType, pin); default : return formatDaily(dashId, deviceId, pinType, pin); } } public static ByteBuffer getByteBufferFromDisk(String dataFolder, User user, int dashId, int deviceId, PinType pinType, byte pin, int count, GraphType type) { Path userDataFile = Paths.get(dataFolder, FileUtils.getUserReportingDir(user), generateFilename(dashId, deviceId, pinType.pintTypeChar, pin, type)); if (Files.notExists(userDataFile)) { return null; } try { return FileUtils.read(userDataFile, count); } catch (IOException ioe) { log.error(ioe); } return null; } private static boolean checkNoData(byte[][] data) { boolean noData = true; for (byte[] pinData : data) { noData = noData && pinData.length == 0; } return noData; } public ByteBuffer getByteBufferFromDisk(User user, int dashId, int deviceId, PinType pinType, byte pin, int count, GraphType type) { return getByteBufferFromDisk(dataFolder, user, dashId, deviceId, pinType, pin, count, type); } public void delete(User user, int dashId, int deviceId, PinType pinType, byte pin) { log.debug("Removing {}{} pin data for dashId {}, deviceId {}.", pinType.pintTypeChar, pin, dashId, deviceId); Path userDataMinuteFile = Paths.get(dataFolder, FileUtils.getUserReportingDir(user), formatMinute(dashId, deviceId, pinType.pintTypeChar, pin)); Path userDataHourlyFile = Paths.get(dataFolder, FileUtils.getUserReportingDir(user), formatHour(dashId, deviceId, pinType.pintTypeChar, pin)); Path userDataDailyFile = Paths.get(dataFolder, FileUtils.getUserReportingDir(user), formatDaily(dashId, deviceId, pinType.pintTypeChar, pin)); FileUtils.deleteQuietly(userDataMinuteFile); FileUtils.deleteQuietly(userDataHourlyFile); FileUtils.deleteQuietly(userDataDailyFile); } protected static String formatMinute(int dashId, int deviceId, char pinType, byte pin) { return format("minute", dashId, deviceId, pinType, pin); } protected static String formatHour(int dashId, int deviceId, char pinType, byte pin) { return format("hourly", dashId, deviceId, pinType, pin); } protected static String formatDaily(int dashId, int deviceId, char pinType, byte pin) { return format("daily", dashId, deviceId, pinType, pin); } private static String format(String type, int dashId, int deviceId, char pinType, byte pin) { //todo this is back compatibility code. should be removed in future versions. if (deviceId == 0) { return "history_" + dashId + "_" + pinType + pin + "_" + type + ".bin"; } return "history_" + dashId + DEVICE_SEPARATOR + deviceId + "_" + pinType + pin + "_" + type + ".bin"; } public void process(User user, int dashId, int deviceId, byte pin, PinType pinType, String value, long ts) { try { double doubleVal = NumberUtil.parseDouble(value); process(user, dashId, deviceId, pin, pinType, value, ts, doubleVal); } catch (Exception e) { //just in case log.trace("Error collecting reporting entry."); } } private void process(User user, int dashId, int deviceId, byte pin, PinType pinType, String value, long ts, double doubleVal) { if (ENABLE_RAW_DB_DATA_STORE) { rawDataProcessor.collect(user, dashId, deviceId, pinType.pintTypeChar, pin, ts, value, doubleVal); } //not a number, nothing to aggregate if (doubleVal == NumberUtil.NO_RESULT) { return; } averageAggregator.collect(user, dashId, deviceId, pinType.pintTypeChar, pin, ts, doubleVal); } public byte[][] getAllFromDisk(User user, GraphPinRequest[] requestedPins) { byte[][] values = new byte[requestedPins.length][]; for (int i = 0; i < requestedPins.length; i++) { final ByteBuffer byteBuffer = getByteBufferFromDisk(user, requestedPins[i].dashId, requestedPins[i].deviceId, requestedPins[i].pinType, requestedPins[i].pin, requestedPins[i].count, requestedPins[i].type); values[i] = byteBuffer == null ? EMPTY_BYTES : byteBuffer.array(); } if (checkNoData(values)) { throw new NoDataException(); } return values; } @Override public void close() { System.out.println("Stopping aggregator..."); this.averageAggregator.close(); } }