/* * Copyright 2009-2020 Contributors (see credits.txt) * * This file is part of jEveAssets. * * jEveAssets is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * jEveAssets is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with jEveAssets; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ package net.nikr.eve.jeveasset.io.local; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.filechooser.FileSystemView; import net.nikr.eve.jeveasset.data.api.raw.RawPublicMarketOrder; import net.nikr.eve.jeveasset.gui.shared.Formater.DateFormatThreadSafe; import net.nikr.eve.jeveasset.gui.tabs.orders.MarketLog; import net.nikr.eve.jeveasset.gui.tabs.orders.OutbidProcesser; import net.nikr.eve.jeveasset.gui.tabs.orders.OutbidProcesser.OutbidProcesserInput; import net.nikr.eve.jeveasset.gui.tabs.orders.OutbidProcesser.OutbidProcesserOutput; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.supercsv.cellprocessor.CellProcessorAdaptor; import org.supercsv.cellprocessor.Optional; import org.supercsv.cellprocessor.ParseBool; import org.supercsv.cellprocessor.ParseDouble; import org.supercsv.cellprocessor.ParseInt; import org.supercsv.cellprocessor.ParseLong; import org.supercsv.cellprocessor.ift.CellProcessor; import org.supercsv.cellprocessor.ift.StringCellProcessor; import org.supercsv.exception.SuperCsvCellProcessorException; import org.supercsv.io.CsvBeanReader; import org.supercsv.io.ICsvBeanReader; import org.supercsv.prefs.CsvPreference; import org.supercsv.util.CsvContext; public class MarketLogReader { private static final Logger LOG = LoggerFactory.getLogger(OutbidProcesser.class); private static final int RETRIES = 3; private static final DateFormatThreadSafe FILE_DATE_FORMAT = new DateFormatThreadSafe("yyyy.MM.dd hhmmss", true); private static final DateFormatThreadSafe DATE_FORMAT = new DateFormatThreadSafe("yyyy-MM-dd HH:mm:ss,SSS"); private final OutbidProcesserInput input; public MarketLogReader(OutbidProcesserInput input) { this.input = input; } public static List<MarketLog> read(File file, OutbidProcesserInput input, OutbidProcesserOutput output) { MarketLogReader reader = new MarketLogReader(input); List<MarketLog> orders = reader.read(file); OutbidProcesser.process(input, output); return orders; } private List<MarketLog> read(final File logFile) { final String filename = logFile.getName(); LOG.info("Reading: " + filename); String[] values = filename.split("-"); if (values.length < 3) { LOG.warn("Failed to read: " + filename); return null; } Date date; try { date = FILE_DATE_FORMAT.parse(values[values.length-1]); } catch (ParseException ex) { LOG.error("Failed to read: " + filename, ex); return null; } List<MarketLog> marketLogs = parse(logFile); if (marketLogs == null || marketLogs.isEmpty()) { LOG.warn("Failed to read: " + filename); return null; } Integer typeID = marketLogs.get(0).getTypeID(); if (typeID == null) { LOG.warn("Failed to read: " + filename); return null; } List<RawPublicMarketOrder> marketOrders = new ArrayList<>(); for (MarketLog marketLog : marketLogs) { marketOrders.add(new RawPublicMarketOrder(marketLog)); } Map<Integer, List<RawPublicMarketOrder>> orders = new HashMap<>(); orders.put(typeID, marketOrders); input.addOrders(orders, date); LOG.info("Read: " + filename); return marketLogs; } private List<MarketLog> parse(File file) { return parse(file, 0); } private List<MarketLog> parse(File file, int retries) { Reader reader = null; ICsvBeanReader beanReader = null; try { beanReader = new CsvBeanReader(new FileReader(file), CsvPreference.STANDARD_PREFERENCE); beanReader.getHeader(true); // the header elements are used to map the values to the bean (names must match) final String[] header = {"price","volRemaining","typeID","range","orderID","volEntered","minVolume","bid","issueDate","duration","stationID","regionID","solarSystemID","jumps", "empty"}; List<MarketLog> marketLogs = new ArrayList<>(); MarketLog marketLog; while ((marketLog = beanReader.read(MarketLog.class, header, getProcessors())) != null) { marketLogs.add(marketLog); } if (marketLogs.isEmpty()) { throw new IllegalArgumentException("Empty file"); } return marketLogs; } catch (SuperCsvCellProcessorException | IOException | IllegalArgumentException ex) { if (retries < RETRIES) { retries++; try { Thread.sleep(retries * 500); } catch (InterruptedException ex1) { //Keep calm and carry on } LOG.info("Retrying: " + retries + " of " + RETRIES + " (" + retries * 500 + ")"); return parse(file, retries); } else { LOG.error(ex.getMessage(), ex); return null; } } finally { if (beanReader != null) { try { beanReader.close(); } catch (IOException ex) { //No problem } } if (reader != null) { try { reader.close(); } catch (IOException ex) { //No problem } } } } private static CellProcessor[] getProcessors() { return new CellProcessor[]{ new ParseDouble(), // price new ParseDouble(), // volRemaining new ParseInt(), // typeID new ParseInt(), // range new ParseLong(), // orderID new ParseInt(), // volEntered new ParseInt(), // minVolume new ParseBool(), // bid new ParseDate(), // issueDate new ParseInt(), // duration new ParseLong(), // stationID new ParseLong(), // regionID new ParseLong(), // solarSystemID new ParseInt(), // jumps new Optional() }; } public static File getMarketlogsDirectory() { //https://wiki.eveuniversity.org/EVE_logs File documents = FileSystemView.getFileSystemView().getDefaultDirectory(); String home = System.getProperty("user.home"); // can be null StringBuilder builder = new StringBuilder(); if (documents.exists()) { builder.append(documents.getPath()); } else { builder.append(home); } if (!new File(documents.getAbsolutePath() + File.separator + "EVE").exists() && new File(documents.getAbsolutePath() + File.separator + "Documents").exists()) { builder.append(File.separator); builder.append("Documents"); } builder.append(File.separator); builder.append("EVE"); builder.append(File.separator); builder.append("logs"); builder.append(File.separator); builder.append("Marketlogs"); return new File(builder.toString()); } @SuppressWarnings("unchecked") public static class ParseDate extends CellProcessorAdaptor implements StringCellProcessor { public static final DateFormatThreadSafe DATETIME = new DateFormatThreadSafe("yyyy-MM-dd hh:mm:ss", true); public static final DateFormatThreadSafe DATE = new DateFormatThreadSafe("yyyy-MM-dd", true); public ParseDate() { super(); } public ParseDate(CellProcessor next) { // this constructor allows other processors to be chained after ParseDay super(next); } @Override public Object execute(Object value, CsvContext context) { if (value == null) { throw new SuperCsvCellProcessorException( "this processor does not accept null input - if the column is optional then chain an Optional() processor before this one", context, this); } Date result; if (value instanceof Date) { result = (Date) value; } else if (value instanceof String) { try { result = DATETIME.parse((String)value); } catch (ParseException ex) { try { result = DATE.parse((String)value); } catch (ParseException ex2) { throw new SuperCsvCellProcessorException( String.format("'%s' could not be parsed as an Date", value), context, this, ex2); } } } else { final String actualClassName = value.getClass().getName(); throw new SuperCsvCellProcessorException(String.format( "the input value should be of type Date or String but is of type %s", actualClassName), context, this); } return next.execute(result, context); } } }