package com.jbidwatcher.auction.server; /* * Copyright (c) 2000-2007, CyberFOX Software, Inc. All Rights Reserved. * * Developed by mrs (Morgan Schweers) */ import com.google.inject.Inject; import com.google.inject.Singleton; import com.jbidwatcher.util.Constants; import com.jbidwatcher.util.PauseManager; import com.jbidwatcher.util.queue.MQFactory; import com.jbidwatcher.util.queue.MessageQueue; import com.jbidwatcher.search.SearchManager; import com.jbidwatcher.util.config.JConfig; import com.jbidwatcher.util.StringTools; import com.jbidwatcher.auction.*; import java.util.*; /** * Simplified on February 17, 2008 to be single-auction-site specific; * JBidwatcher is not used on any other auction sites than eBay, and hasn't * been for many years. */ @Singleton public class AuctionServerManager implements MessageQueue.Listener, Resolver { private final EntryFactory entryFactory; private EntryManager entryManager = null; private AuctionServer mServer = null; private SearchManager searcher; private PauseManager pauseManager; @Inject private AuctionServerManager(EntryManager entryManager, SearchManager searcher, EntryFactory entryFactory, PauseManager pauseManager) { this.entryManager = entryManager; this.searcher = searcher; this.entryFactory = entryFactory; this.pauseManager = pauseManager; this.entryFactory.setResolver(this); MQFactory.getConcrete("auction_manager").registerListener(this); } private Map<String, Long> timingLog = new HashMap<String, Long>(); private final Map<String, Long> startLog = new HashMap<String, Long>(); private Map<String, Long> countLog = new HashMap<String, Long>(); private Map<String, LinkedList<Long>> last10Log = new HashMap<String, LinkedList<Long>>(); private void timeStart(String blockName) { synchronized(startLog) { startLog.put(blockName, System.currentTimeMillis()); } } private void timeStop(String blockName) { synchronized (startLog) { long now = System.currentTimeMillis(); long started = startLog.get(blockName); startLog.remove(blockName); long accum = timingLog.containsKey(blockName) ? timingLog.get(blockName) : 0; accum += (now - started); LinkedList<Long> last10 = last10Log.get(blockName); if (last10 == null) last10 = new LinkedList<Long>(); last10.add(now - started); if (last10.size() > 10) last10.removeFirst(); last10Log.put(blockName, last10); timingLog.put(blockName, accum); countLog.put(blockName, (countLog.containsKey(blockName) ? countLog.get(blockName) + 1 : 1)); } } private void timeDump(String last10From) { synchronized(startLog) { for (Map.Entry<String, Long> segment : timingLog.entrySet()) { long accum = segment.getValue(); long count = countLog.get(segment.getKey()); Double avg = (accum * 1.0) / (count * 1.0); JConfig.log().logDebug(segment.getKey() + ": " + avg + " x " + count + "(" + segment.getValue() + ")"); } JConfig.log().logDebug("Last 10 from " + last10From + ": " + StringTools.comma(last10Log.get(last10From))); } } private abstract class Report { public abstract void report(AuctionEntry ae, int count); } public void loadAuctionsFromDB(final AuctionServer newServer) { MQFactory.getConcrete("splash").enqueue("SET 0"); timeStart("counts"); // True up the Auction Entries first. I want this to not be necessary anymore. EntryCorral.trueUpEntries(); int entryCount = EntryCorral.count(); // Metrics JConfig.getMetrics().trackCustomData("categories", Integer.toString(Category.count(Category.class))); JConfig.getMetrics().trackCustomData("entries", Integer.toString(entryCount)); int auctionCount = AuctionInfo.count(); int uniqueEntries = EntryCorral.uniqueCount(); int activeEntries = EntryCorral.activeCount(); // Metrics JConfig.getMetrics().trackCustomData("active", Integer.toString(activeEntries)); JConfig.getMetrics().trackCustomData("sniped", Integer.toString(EntryCorral.snipedCount())); int uniqueCount = AuctionInfo.uniqueCount(); timeStop("counts"); if (JConfig.queryConfiguration("stats.auctions") == null) JConfig.setConfiguration("stats.auctions", Long.toString(uniqueEntries)); JConfig.log().logMessage("Loading listings from the database (" + activeEntries + "/" + uniqueEntries + "/" + entryCount + " entries, " + uniqueCount + "/" + auctionCount + " auctions)"); timeStart("findAll"); List<AuctionEntry> entries = EntryCorral.findActive(); //TODO EntryCorral these timeStop("findAll"); timeStart("findAuctions"); connectEntries(entries); timeStop("findAuctions"); final List<AuctionEntry> sniped = new ArrayList<AuctionEntry>(); JConfig.log().logMessage("Done with the initial load (got " + entries.size() + " active entries)"); importListingsToUI(newServer, entries, new Report() { public void report(AuctionEntry ae, int count) { MQFactory.getConcrete("splash").enqueue("SET " + count); if (!ae.isComplete() && ae.isSniped()) { sniped.add(ae); } } }); JConfig.log().logDebug("Auction Entries loaded"); spinOffCompletedLoader(newServer); JConfig.log().logDebug("Completed loader spun off"); for(AuctionEntry snipable:sniped) { timeStart("snipeSetup"); if(!snipable.isComplete()) { snipable.refreshSnipe(); } timeStop("snipeSetup"); } JConfig.log().logDebug("Snipes processed"); timeDump("addEntry"); } private void connectEntries(List<AuctionEntry> entries) { List<String> auctionIDs = new ArrayList<String>(entries.size()); for(AuctionEntry entry : entries) { auctionIDs.add(entry.getAuctionId()); } List<AuctionInfo> auctions = AuctionInfo.findAllByIds(auctionIDs); Map<String, AuctionInfo> joining = new HashMap<String, AuctionInfo>(entries.size()); for(AuctionInfo info : auctions) { joining.put(info.getId().toString(), info); } for (AuctionEntry entry : entries) { AuctionInfo ai = joining.get(entry.getAuctionId()); entry.setAuctionInfo(ai); } } private void spinOffCompletedLoader(final AuctionServer newServer) { Thread completedHandler = new Thread() { public void run() { final MessageQueue tabQ = MQFactory.getConcrete("complete Tab"); tabQ.enqueue("REPORT Importing completed listings"); tabQ.enqueue("SHOW"); timeStart("findEnded"); List<AuctionEntry> entries = EntryCorral.findEnded();//TODO EntryCorral these? timeStop("findEnded"); int endedCount = entries.size(); final double percentStep = ((double)endedCount) / 100.0; final double percentMultiple = 100.0 / ((double)endedCount); tabQ.enqueue("PROGRESS"); tabQ.enqueue("PROGRESS Loading..."); importListingsToUI(newServer, entries, new Report() { public void report(AuctionEntry ae, int count) { if(percentStep < 1.0) { tabQ.enqueue("PROGRESS " + Math.round(count * percentMultiple)); } else { if(count % (Math.round(percentStep)) == 0) { tabQ.enqueue("PROGRESS " + Math.round(count / percentStep)); } } try { Thread.sleep(50); } catch(InterruptedException ie) { /* ignore */ } } }); tabQ.enqueue("HIDE"); EntryTable.getRealDatabase().commit(); } }; Thread lostHandler = new Thread() { public void run() { List<AuctionInfo> lostAuctions = AuctionInfo.findLostAuctions(); if(lostAuctions != null && !lostAuctions.isEmpty()) { JConfig.log().logMessage("Recovering " + lostAuctions.size() + " listings."); for (AuctionInfo ai : lostAuctions) { AuctionEntry ae = entryFactory.constructEntry(); ae.setAuctionInfo(ai); ae.setCategory("recovered"); ae.setSticky(true); ae.setNeedsUpdate(); entryManager.addEntry(ae); } MQFactory.getConcrete("recovered Tab").enqueue("REPORT These auctions had lost their settings."); MQFactory.getConcrete("recovered Tab").enqueue("SHOW"); EntryTable.getRealDatabase().commit(); } } }; completedHandler.start(); lostHandler.start(); } private void importListingsToUI(AuctionServer newServer, List<AuctionEntry> entries, Report r) { int count = 0; for(AuctionEntry ae : entries) { timeStart("setServer"); ae.setServer(newServer); timeStop("setServer"); if (!ae.hasAuction()) { JConfig.log().logMessage("We lost the underlying auction for: " + ae.dumpRecord()); boolean recentlyUpdated = ae.getLastUpdated() != null && ae.getLastUpdated().after(new Date(System.currentTimeMillis() - Constants.ONE_DAY * 45)); if(ae.getString("identifier") != null && recentlyUpdated) { JConfig.log().logMessage("Trying to reload auction via its auction identifier."); MQFactory.getConcrete("drop").enqueue(ae.getString("identifier")); } else { if(!recentlyUpdated) { JConfig.log().logMessage("Auction entry " + ae.getString("identifier") + " is too old to reload, deleting."); } else { JConfig.log().logMessage("Auction entry id " + ae.getId() + " doesn't have enough detail to reload; deleting."); } timeStart("delete"); ae.delete(); timeStop("delete"); } } else { timeStart("addEntry"); timeStart("addEntry-" + ae.getCategory()); try { entryManager.addEntry(ae); } catch(Exception e) { String errorMessage = "Failed to add an auction entry"; errorMessage += " for item " + ae.getIdentifier() + " (" + ae.getId() + ") "; JConfig.log().handleException(errorMessage, e); } timeStop("addEntry-" + ae.getCategory()); timeStop("addEntry"); } if(r != null) r.report(ae, count++); } } /** * @brief Serialize access to the time updating function, so that * everybody in the world doesn't try to update the time all at * once, like they used to. Four threads trying to update the time * all together caused some nasty errors. */ public void messageAction(Object deQ) { String cmd = (String)deQ; if(cmd.equals("TIMECHECK")) { if(pauseManager.isPaused()) { // Punt, and let the time drift until the next update. return; } AuctionServerInterface defaultServer = getServer(); defaultServer.reloadTime(); long servTime = defaultServer.getServerTimeDelta(); Date now = new Date(System.currentTimeMillis() + servTime); MQFactory.getConcrete("Swing").enqueue("Server time is now: " + now); } } public String getDefaultServerTime() { AuctionServerInterface defaultServer = getServer(); return defaultServer.getTime(); } public AuctionServer setServer(AuctionServer aucServ) { if(mServer != null) { //noinspection ThrowableInstanceNeverThrown RuntimeException here = new RuntimeException("Trying to add a server, when we've already got one!"); JConfig.log().handleException("setServer error!", here); return mServer; } mServer = aucServ; mServer.addSearches(searcher); return(mServer); } public AuctionServer getServer() { return mServer; } public ServerMenu addAuctionServerMenus() { return mServer.establishMenu(); } public void cancelSearches() { mServer.cancelSearches(); } public AuctionStats getStats() { AuctionStats outStat = new AuctionStats(); outStat._count = EntryCorral.count(); outStat._completed = EntryCorral.completedCount(); outStat._snipes = EntryCorral.snipedCount(); outStat._nextSnipe = EntryCorral.nextSniped(); //TODO EntryCorral this? outStat._nextEnd = null; outStat._nextUpdate = null; return outStat; } }