Package com.jbidwatcher.ui

Source Code of com.jbidwatcher.ui.AuctionsManager

package com.jbidwatcher.ui;
/*
* Copyright (c) 2000-2007, CyberFOX Software, Inc. All Rights Reserved.
*
* Developed by mrs (Morgan Schweers)
*/

/*!@class AuctionsManager
* @brief AuctionsManager abstracts group functionality for all
* managed groups of auctions
*
* So, for example, it supports searching all groups of auctions for
* outstanding snipes, for snipes that need to fire, for removing,
* verifying, adding, and retrieving auctions, and similar features
*/

import com.cyberfox.util.platform.Path;
import com.jbidwatcher.util.PauseManager;
import com.jbidwatcher.util.config.*;
import com.jbidwatcher.util.queue.*;
import com.jbidwatcher.util.xml.XMLElement;
import com.jbidwatcher.util.xml.XMLParseException;
import com.jbidwatcher.util.Constants;
import com.jbidwatcher.auction.server.AuctionServerManager;
import com.jbidwatcher.auction.server.AuctionStats;
import com.jbidwatcher.auction.server.AuctionServer;
import com.jbidwatcher.auction.*;

import java.io.*;
import java.util.*;
import java.text.SimpleDateFormat;

/** @noinspection Singleton*/
public class AuctionsManager implements TimerHandler.WakeupProcess, EntryManager, JConfig.ConfigListener, MessageQueue.Listener {
  private static AuctionsManager mInstance = null;
  private FilterManager mFilter;

  //  Checkpoint (save) every N minutes where N is configurable.
  private long mCheckpointFrequency;
  private long mLastCheckpointed = 0;
  private static final int AUCTIONCOUNT = 100;
  private static final int MAX_PERCENT = AUCTIONCOUNT;
  private static TimerHandler sTimer = null;
  private final PauseManager mPauseManager = PauseManager.getInstance();

  /**
   * @brief AuctionsManager is a singleton, there should only be one
   * in the system.
   */
  private AuctionsManager() {
    //  This should be loaded from the configuration settings.
    mCheckpointFrequency = 10 * Constants.ONE_MINUTE;
    mLastCheckpointed = System.currentTimeMillis();

    mFilter = new FilterManager();

    MQFactory.getConcrete("manager").registerListener(this);
  }

  static {
    mInstance = new AuctionsManager();
  }

  public void messageAction(Object deQ) {
    String identifier = (String)deQ;

    AuctionEntry ae = EntryCorral.getInstance().takeForRead(identifier);
    addEntry(ae);
  }

  /**
   * @brief The means of getting access to the functions of
   * AuctionsManager, as a Singleton.
   *
   * @return The one reference to this object.
   */
  public static AuctionsManager getInstance() {
    return mInstance;
  }

  public FilterManager getFilters() {
    return mFilter;
  }

  /////////////////////////////////////////////////////////
  //  Mass-equivalents for Auction-list specific operations

  /**
   * @brief Check if it's time to save the auctions out yet.
   */
  private void checkSnapshot() {
    if( (mLastCheckpointed + mCheckpointFrequency) < System.currentTimeMillis() ) {
      mLastCheckpointed = System.currentTimeMillis();
//      saveAuctions();
      System.gc();
    }
  }

  private List<AuctionEntry> normalizeEntries(List<AuctionEntry> entries) {
    List<AuctionEntry> output = new ArrayList<AuctionEntry>();
    for(AuctionEntry ae : entries) {
      output.add(EntryCorral.getInstance().takeForRead(ae.getIdentifier()));
    }
    return output;
  }

  /**
   * @brief Check all the auctions for active events, and check if we
   * should snapshot the auctions off to disk.
   *
   * @return True if any auctions updated.
   */
  public boolean check() throws InterruptedException {
    boolean neededUpdate = false;
    List<AuctionEntry> needUpdate;
    if(!mPauseManager.isPaused()) {
      needUpdate = normalizeEntries(AuctionEntry.findAllNeedingUpdates(Constants.ONE_MINUTE * 69)); // TODO: Simplify to load just identifiers?
      updateList(needUpdate);
      neededUpdate = !needUpdate.isEmpty();

      //  These could be two separate threads, doing slow and fast updates.
      needUpdate = normalizeEntries(AuctionEntry.findEndingNeedingUpdates(Constants.ONE_MINUTE));
      updateList(needUpdate);
      neededUpdate |= !needUpdate.isEmpty();
    }

    //  Or three, doing slow, fast, and manual...
    needUpdate = normalizeEntries(AuctionEntry.findManualUpdates());
    updateList(needUpdate);
    neededUpdate |= !needUpdate.isEmpty();

    checkSnapshot();

    return neededUpdate;
  }

  private void updateList(List<AuctionEntry> needUpdate) throws InterruptedException {
    for(AuctionEntry ae : needUpdate) {
      if (Thread.interrupted()) throw new InterruptedException();
      // It's likely that we've pulled a big list of stuff to update before realizing the
      // networking is down; pause updating for a little bit until it's likely to have come
      // back.
      if (!mPauseManager.isPaused()) {
        boolean forced = ae.isUpdateRequired();

        MQFactory.getConcrete("update " + ae.getCategory()).enqueue("start " + ae.getIdentifier());

        Auctions.doUpdate(ae);
        EntryCorral.getInstance().putWeakly(ae);

        MQFactory.getConcrete("update " + ae.getCategory()).enqueue("stop " + ae.getIdentifier());

        if (forced) MQFactory.getConcrete("redraw").enqueue(ae.getCategory()); // Redraw a tab that has a forced update.
      }
    }
  }

  /**
   * @brief Add a new auction entry to the set.
   *
   * This is complex mainly because the splash screen needs to be
   * updated if we're loading from XML at startup, and because the new
   * auction type needs to be split across the hardcoded auction
   * collection types.
   *
   * @param ae - The auction entry to add.
   */
  public void addEntry(AuctionEntry ae) {
    mFilter.addAuction(ae);
  }

  /**
   * @brief Delete from ALL auction lists!
   *
   * The FilterManager does this, as it needs to be internally
   * self-consistent.
   *
   * @param ae - The auction entry to delete.
   */
  public void delEntry(AuctionEntry ae) {
    String id = ae.getIdentifier();
    DeletedEntry.create(id);
    ae.cancelSnipe(false);
    mFilter.deleteAuction(ae);
    ae.delete();
  }

  /**
   * @brief Load auctions from a save file, with a pretty splash
   * screen and everything, if necessary.
   *
   * I'd like to abstract this, and make it work with arbitrary
   * streams, so that we could send an XML file of auctions over a
   * network to sync between JBidwatcher instances.
   */
  public void loadAuctions() {
    XMLElement xmlFile = new XMLElement(true);
    String loadFile = JConfig.queryConfiguration("savefile", "auctions.xml");
    String oldLoad = loadFile;

    loadFile = Path.getCanonicalFile(loadFile, "jbidwatcher", true);
    if(!loadFile.equals(oldLoad)) {
      JConfig.setConfiguration("savefile", loadFile);
    }

    File toLoad = new File(loadFile);
    if(toLoad.exists() && toLoad.length() != 0) {
      try {
        loadXMLFromFile(loadFile, xmlFile);
      } catch(IOException ioe) {
        JConfig.log().handleException("A serious problem occurred trying to load from auctions.xml.", ioe);
        MQFactory.getConcrete("Swing").enqueue("ERROR Failure to load your saved auctions.  Some or all items may be missing.");
      } catch(XMLParseException xme) {
        JConfig.log().handleException("Trying to load from auctions.xml.", xme);
        MQFactory.getConcrete("Swing").enqueue("ERROR Failure to load your saved auctions.  Some or all items may be missing.");
      }
    } else {
      //  This is a common thing, and we don't want to frighten new
      //  users, who are most likely to see it.
      JConfig.log().logDebug("JBW: Failed to load saved auctions, the auctions file is probably not there yet.");
      JConfig.log().logDebug("JBW: This is not an error, unless you're constantly getting it.");
    }
  }

  public int loadAuctionsFromDatabase() {
    int totalCount = AuctionInfo.count();
    int activeCount = AuctionEntry.activeCount();

    MQFactory.getConcrete("splash").enqueue("WIDTH " + activeCount);
    MQFactory.getConcrete("splash").enqueue("SET 0");

    AuctionServer newServer = AuctionServerManager.getInstance().getServer();
    AuctionServerManager.setEntryManager(this);
    if (totalCount == 0) {
      if(JConfig.queryConfiguration("stats.auctions") == null) JConfig.setConfiguration("stats.auctions", "0");
      return 0;
    }

    AuctionServerManager.getInstance().loadAuctionsFromDB(newServer);
    AuctionStats as = AuctionServerManager.getInstance().getStats();

    int savedCount = Integer.parseInt(JConfig.queryConfiguration("last.auctioncount", "-1"));
    if (as != null) {
      if (savedCount != -1 && as.getCount() != savedCount) {
        MQFactory.getConcrete("Swing").enqueue("NOTIFY Failed to load all auctions from database.");
      }
    }

    return activeCount;
  }

  private void loadXMLFromFile(String loadFile, XMLElement xmlFile) throws IOException {
    InputStreamReader isr = new InputStreamReader(new FileInputStream(loadFile));
    MQFactory.getConcrete("splash").enqueue("WIDTH " + MAX_PERCENT);
    MQFactory.getConcrete("splash").enqueue("SET " + MAX_PERCENT / 2);

    xmlFile.parseFromReader(isr);
    MQFactory.getConcrete("splash").enqueue("SET " + MAX_PERCENT);

    String formatVersion = xmlFile.getProperty("FORMAT", "0101");
    XMLElement auctionsXML = xmlFile.getChild("auctions");
    JConfig.setConfiguration("savefile.format", formatVersion);
    //  set the width of the splash progress bar based on the number
    //  of auctions that will be loaded!
    if (auctionsXML == null) {
      throw new XMLParseException(xmlFile.getTagName(), "AuctionsManager requires an <auctions> tag!");
    }
    String auctionQuantity = auctionsXML.getProperty("COUNT", null);

    int auctionTotal = 0;
    if(auctionQuantity != null) {
      auctionTotal = Integer.parseInt(auctionQuantity);
      MQFactory.getConcrete("splash").enqueue("SET 0");
      MQFactory.getConcrete("splash").enqueue("WIDTH " + auctionTotal);
    }

    AuctionServerManager.setEntryManager(this);
    AuctionServerManager.getInstance().fromXML(auctionsXML);

    AuctionStats as = AuctionServerManager.getInstance().getStats();

    int savedCount = Integer.parseInt(JConfig.queryConfiguration("last.auctioncount", "-1"));
    if(as != null) {
      if(as.getCount() != auctionTotal || (savedCount != -1 && as.getCount() != savedCount)) {
        MQFactory.getConcrete("Swing").enqueue("NOTIFY Failed to load all auctions from XML file.");
      }
    }
  }

  //  Reuse a single save buffer when writing out the auctions.xml file.
  private static final int ONEK = 1024;
  private static final StringBuffer _saveBuf = new StringBuffer(AUCTIONCOUNT *ONEK);

  /**
   * @brief Save auctions out to the savefile, in XML format.
   *
   * Similar to the loadAuctions code, this would be nice if it were
   * abstracted to write to any outputstream, allowing us to write to
   * a remote node to update it with our auctions and snipes.
   *
   * @return - the filename if it successfully saved, null if an error occurred.
   */
  public String saveAuctions() {
    XMLElement auctionsData = AuctionServerManager.getInstance().toXML();
    String oldSave = JConfig.queryConfiguration("savefile", "auctions.xml");
    String saveFilename = Path.getCanonicalFile(JConfig.queryConfiguration("savefile", "auctions.xml"), "jbidwatcher", false);
    String newSave=saveFilename;

    //  If there's no data to save, then pretend we did it.
    if(auctionsData == null) return saveFilename;

    ensureDirectories(saveFilename);

    boolean swapFiles = needSwapSaves(saveFilename);

    if(!saveFilename.equals(oldSave)) {
      JConfig.setConfiguration("savefile", saveFilename);
    }

    //  If we already have a save file, preserve its name, and write
    //  the new one to '.temp'.
    if(swapFiles) {
      newSave = saveFilename + ".temp";
      File newSaveFile = new File(newSave);
      if(newSaveFile.exists()) newSaveFile.delete();
    }

    StringBuffer buf = buildSaveBuffer(auctionsData);
    boolean saveDone = true;

    //  Dump the save file out!
    try {
      PrintStream ps = new PrintStream(new FileOutputStream(newSave));
      ps.println(buf);
      ps.close();
    } catch(IOException e) {
      JConfig.log().handleException("Failed to save auctions.", e);
      saveDone = false;
    }

    //  If the save was complete, and we have to swap old/new files,
    //  then [remove prior '.old' file if necessary], save current XML
    //  as '.old', and move most recent save file to be just a normal
    //  save file.
    if(saveDone && swapFiles) {
      preserveFiles(saveFilename);
    }

    return saveFilename;
  }

  public int clearDeleted() {
    int rval = DeletedEntry.clear();

    saveAuctions();
    System.gc();

    return rval;
  }

  private static void ensureDirectories(String saveFilename) {
    //  Thanks to Gabor Liptak for this recommendation...
    File saveParent = new File(saveFilename);
    saveParent = saveParent.getParentFile();
    if(!saveParent.exists()) saveParent.mkdirs(); //  This can fail, but we don't mind.
  }

  public static StringBuffer buildSaveBuffer(XMLElement auctionsData) {
    synchronized(_saveBuf) {
      _saveBuf.setLength(0);
      _saveBuf.append("<?xml version=\"1.0\"?>\n\n");
      _saveBuf.append(Constants.XML_SAVE_DOCTYPE);
      _saveBuf.append('\n');
      _saveBuf.append("<jbidwatcher format=\"0101\">\n");
      auctionsData.toStringBuffer(_saveBuf, 1);
      _saveBuf.append("</jbidwatcher>");
    }
    return _saveBuf;
  }

  private static boolean needSwapSaves(String saveName) {
    File oldFile = new File(saveName);
    return oldFile.exists();
  }

  private static void preserveFiles(String filename) {
    File oldFile = new File(filename);
    File saveFile = new File(filename + ".temp");
    SimpleDateFormat sdf = new SimpleDateFormat("ddMMMyy_HHmm");
    String nowStr = sdf.format(new Date());
    String retainFilename = makeBackupFilename(filename, nowStr);
    File retainFile = new File(retainFilename);
    if(retainFile.exists()) retainFile.delete();

    String oldestSave = JConfig.queryConfiguration("save.file.4", "");
    if(oldestSave.length() != 0) {
      File oldest = new File(oldestSave);
      if(oldest.exists()) {
        backupByDate(filename, oldest);
      }
    }

    for(int i=4; i>0; i--) {
      JConfig.setConfiguration("save.file." + i, JConfig.queryConfiguration("save.file." + (i-1), ""));
    }

    File keepFile = new File(retainFilename);
    if(!oldFile.renameTo(keepFile)) {
      JConfig.log().logDebug("Renaming the old file (" + oldFile + ") to the retain file (" + keepFile + ") failed!");
    }
    JConfig.setConfiguration("save.file.0", retainFilename);

    File standard = new File(filename);
    if(!saveFile.renameTo(standard)) {
      JConfig.log().logDebug("Renaming the new file (" + saveFile + ") to the standard filename (" + standard + ") failed!");
    }
  }

  private static void backupByDate(String filename, File oldest) {
    SimpleDateFormat justDateFmt = new SimpleDateFormat("ddMMMyy");
    String justDate = justDateFmt.format(new Date());
    String oldBackup = makeBackupFilename(filename, justDate);
    File oldDateBackup = new File(oldBackup);
    if(oldDateBackup.exists()) {
      oldDateBackup.delete();
      File newDateBackup = new File(oldBackup);
      oldest.renameTo(newDateBackup);
    } else {
      oldest.renameTo(oldDateBackup);
      String oldestByDate = JConfig.queryConfiguration("save.bydate.4", "");
      for(int i=4; i>0; i--) {
        JConfig.setConfiguration("save.bydate." + i, JConfig.queryConfiguration("save.bydate." + (i-1), ""));
      }
      JConfig.setConfiguration("save.bydate.0", oldBackup);
      File deleteMe = new File(oldestByDate);
      deleteMe.delete();
    }
  }

  private static String makeBackupFilename(String filename, String toInsert) {
    int lastSlash = filename.lastIndexOf(System.getProperty("file.separator"));
    if(lastSlash == -1) {
      JConfig.log().logDebug("Filename has no separators: " + filename);
      lastSlash = 0;
    }
    int firstDot = filename.indexOf('.', lastSlash);
    if(firstDot == -1) {
      JConfig.log().logDebug("Filename has no dot/extension: " + filename);
      firstDot = filename.length();
    }

    return filename.substring(0, firstDot) + '-' + toInsert + filename.substring(firstDot);
  }

  public static void start() {
    if(sTimer == null) {
      sTimer = new TimerHandler(getInstance());
      sTimer.setName("Updates");
      sTimer.start();
    }
    JConfig.registerListener(getInstance());
  }

  public void updateConfiguration() {
    String newSnipeTime = JConfig.queryConfiguration("snipemilliseconds");
    if(newSnipeTime != null) {
      AuctionEntry.setDefaultSnipeTime(Long.parseLong(newSnipeTime));
    }
  }
}
TOP

Related Classes of com.jbidwatcher.ui.AuctionsManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.