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.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.jbidwatcher.util.PauseManager;
import com.jbidwatcher.util.Record;
import com.jbidwatcher.util.config.*;
import com.jbidwatcher.util.queue.*;
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 org.json.simple.JSONObject;
import java.util.*;
@Singleton
public class AuctionsManager implements TimerHandler.WakeupProcess, EntryManager, JConfig.ConfigListener, MessageQueue.Listener {
private FilterManager mFilter;
private final PauseManager mPauseManager;
private final EntryCorral entryCorral;
private final Provider<AuctionServerManager> serverManagerProvider;
// Checkpoint (save) every N minutes where N is configurable.
private long mCheckpointFrequency;
private long mLastCheckpointed = 0;
private static TimerHandler sTimer = null;
/**
* @brief AuctionsManager is a singleton, there should only be one
* in the system.
*/
@Inject
private AuctionsManager(FilterManager filterManager, PauseManager pauseManager, EntryCorral corral, Provider<AuctionServerManager> serverManagerProvider) {
// This should be loaded from the configuration settings.
mCheckpointFrequency = 10 * Constants.ONE_MINUTE;
mLastCheckpointed = System.currentTimeMillis();
mPauseManager = pauseManager;
mFilter = filterManager;
entryCorral = corral;
this.serverManagerProvider = serverManagerProvider;
MQFactory.getConcrete("manager").registerListener(this);
}
public void messageAction(Object deQ) {
String identifier = (String)deQ;
AuctionEntry ae = entryCorral.takeForRead(identifier);
addEntry(ae);
}
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.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(EntryCorral.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(EntryCorral.findEndingNeedingUpdates(Constants.ONE_MINUTE));
updateList(needUpdate);
neededUpdate |= !needUpdate.isEmpty();
}
// Or three, doing slow, fast, and manual...
needUpdate = normalizeEntries(EntryCorral.findManualUpdates());
updateList(needUpdate);
neededUpdate |= !needUpdate.isEmpty();
checkSnapshot();
return neededUpdate;
}
/**
* It's time to update, so show that we're updating this auction,
* update it, filter it to see if it needs to move (i.e. is
* completed), and then let the user know we finished.
*
* @param ae - The auction to update.
*/
public void doUpdate(AuctionEntry ae) {
String titleWithComment = ae.getTitleAndComment();
if (!ae.isComplete() || ae.isUpdateRequired()) {
MQFactory.getConcrete("Swing").enqueue("Updating " + titleWithComment);
MQFactory.getConcrete("redraw").enqueue(ae.getIdentifier());
Thread.yield();
Record before = ae.getBacking();
ae.update();
Record after = ae.getBacking();
// TODO(mschweers) - This probably detects too much, like timestamp changes, etc... Need to test.
boolean same = JSONObject.toJSONString(after).equals(JSONObject.toJSONString(before));
MQFactory.getConcrete("my").enqueue("UPDATE " + ae.getIdentifier() + "," + Boolean.toString(!same));
if (!same) {
// Forget any cached info we have; the on-disk version has changed.
String category = ae.getCategory();
MQFactory.getConcrete("redraw").enqueue(category);
}
ae = (AuctionEntry) entryCorral.takeForWrite(ae.getIdentifier()); // Lock the item
entryCorral.erase(ae.getIdentifier());
MQFactory.getConcrete("redraw").enqueue(ae.getIdentifier());
MQFactory.getConcrete("Swing").enqueue("Done updating " + ae.getTitleAndComment());
}
}
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());
doUpdate(ae);
entryCorral.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.
*
* @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();
}
public int loadAuctionsFromDatabase() {
int totalCount = AuctionInfo.count();
int activeCount = EntryCorral.activeCount();
MQFactory.getConcrete("splash").enqueue("WIDTH " + activeCount);
MQFactory.getConcrete("splash").enqueue("SET 0");
AuctionServer newServer = serverManagerProvider.get().getServer();
if (totalCount == 0) {
if(JConfig.queryConfiguration("stats.auctions") == null) JConfig.setConfiguration("stats.auctions", "0");
return 0;
}
serverManagerProvider.get().loadAuctionsFromDB(newServer);
AuctionStats as = serverManagerProvider.get().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;
}
public int clearDeleted() {
int rval = DeletedEntry.clear();
System.gc();
return rval;
}
public void start() {
if(sTimer == null) {
sTimer = new TimerHandler(this);
sTimer.setName("Updates");
sTimer.start();
}
JConfig.registerListener(this);
}
public void updateConfiguration() {
String newSnipeTime = JConfig.queryConfiguration("snipemilliseconds");
if(newSnipeTime != null) {
AuctionEntry.setDefaultSnipeTime(Long.parseLong(newSnipeTime));
}
}
}