package transparent.core; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.InputStreamReader; import java.io.IOException; import java.math.BigInteger; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import org.fusesource.jansi.AnsiConsole; import org.simpleframework.http.core.ContainerServer; import org.simpleframework.transport.connect.Connection; import org.simpleframework.transport.connect.SocketConnection; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import transparent.core.PriceHistory.PriceRecord; import transparent.core.database.Database; import transparent.core.database.Database.Relation; import transparent.core.database.Database.Results; import transparent.core.database.MariaDBDriver; public class Core { public static final byte PRODUCT_LIST_REQUEST = 0; public static final byte PRODUCT_INFO_REQUEST = 1; public static final String NEWLINE = System.getProperty("line.separator"); private static final String SEED_KEY = "seed"; private static final String MODULE_COUNT = "modules.count"; private static final String RUNNING_TASKS = "running"; private static final String QUEUED_TASKS = "queued"; private static final String SCRIPT_FLAG = "--script"; private static final String HELP_FLAG = "--help"; private static final int THREAD_POOL_SIZE = 64; private static final int HTTP_SERVER_PORT = 16317; private static final int MAX_IMAGE_SIZE = 1 << 24; private static final BigInteger HUNDRED_QUADRILLION = new BigInteger("100000000000000000"); private static final BigInteger ONE = new BigInteger("1"); private static final String IMAGE_PATH = "/var/www/localhost/htdocs/"; private static final String IMAGE_WEB_PATH = "http://theflowers.us.to/"; private static final String DEFAULT_SCRIPT = "rc.transparent"; private static final String SPHINX_PROCESS = "searchd"; private static final String SPHINX_COMMAND = SPHINX_PROCESS + " --config index/sphinx.conf"; private static final String REDIS_PROCESS = "redis-server"; private static final String REDIS_COMMAND = "/usr/sbin/redis-server redis/redis.conf"; private static final Sandbox sandbox = new NoSandbox(); private static Database database; private static ScheduledExecutorService dispatcher = Executors.newScheduledThreadPool(THREAD_POOL_SIZE); private static ReentrantLock tasksLock = new ReentrantLock(); private static ReentrantLock imageQueueLock = new ReentrantLock(); private static JedisPool pool; private static long seed = 0; /* needed to print unsigned 64-bit long values */ private static final BigInteger B64 = BigInteger.ZERO.setBit(64); /* data structures to keep track of modules */ private static ReentrantLock modulesLock = new ReentrantLock(); private static ConcurrentHashMap<Long, Module> modules = new ConcurrentHashMap<Long, Module>(); /* represents the list encoding of modules in the database */ private static ArrayList<Module> moduleList = new ArrayList<Module>(); /* represents the list encoding of tasks in the database */ private static ArrayList<Task> queuedList = new ArrayList<Task>(); private static ArrayList<Task> runningList = new ArrayList<Task>(); public static InetAddress FRONTEND_ADDRESS = null; public static String toUnsignedString(long value) { if (value >= 0) return String.valueOf(value); return BigInteger.valueOf(value).add(B64).toString(); } /** * Bit-shift random number generator with period 2^64 - 1. * @see http://www.javamex.com/tutorials/random_numbers/xorshift.shtml */ public static long random() { seed ^= (seed << 21); seed ^= (seed >>> 35); seed ^= (seed << 4); if (database != null && !database.setMetadata("seed", toUnsignedString(seed))) Console.printWarning("Core", "random", "Cannot save new seed."); return seed; } private static void loadSeed() { if (database == null) { Console.printError("Core", "loadSeed", "No database available. Generating temporary seed..."); while (seed == 0) seed = System.nanoTime(); return; } String seedValue = database.getMetadata(SEED_KEY); if (seedValue == null) { while (seed == 0) seed = System.nanoTime(); database.setMetadata(SEED_KEY, Long.toString(seed)); } else { try { seed = new BigInteger(seedValue).longValue(); } catch (NumberFormatException e) { Console.printWarning("Core", "loadSeed", "Unable to read seed, regenerating..."); while (seed == 0) seed = System.nanoTime(); database.setMetadata(SEED_KEY, Long.toString(seed)); } } Console.flush(); } public static void execute(Runnable task) { dispatcher.execute(task); } public static boolean loadModules() { if (database == null) { Console.printError("Core", "loadModules", "No database available. No modules loaded."); return false; } modulesLock.lock(); try { Console.println("Loading modules..."); String moduleCountString = database.getMetadata(MODULE_COUNT); if (moduleCountString == null) { Console.printError("Core", "loadModules", "No modules stored."); return false; } int moduleCount; try { moduleCount = Integer.parseInt(moduleCountString); } catch (NumberFormatException e) { Console.printError("Core", "loadModules", "Could not parse module count."); return false; } Console.println("Found " + moduleCount + " modules."); moduleList.ensureCapacity(moduleCount); for (int i = 0; i < moduleCount; i++) { Module module = Module.load(database, i); if (module == null) { Console.printError("Core", "loadModules", "Error loading module at index " + i + "."); return false; } Console.println("Loaded module '" + module.getModuleName() + "' (id: " + module.getIdString() + ")"); Module old = modules.put(module.getId(), module); if (old != null) { Console.printError("Core", "loadModules", "Module with id " + old.getIdString() + " (name: '" + old.getModuleName() + "')" + " already exists. Skipping..."); modules.put(module.getId(), old); continue; } moduleList.add(module); } } finally { modulesLock.unlock(); } Console.println("Done loading modules."); return true; } public static boolean addModule(Module module) { modulesLock.lock(); try { Module old = modules.put(module.getId(), module); if (old != null) { modules.put(old.getId(), old); Console.printError("Core", "addModule", "Module with id " + module.getIdString() + " already exists. " + "(name: '" + old.getModuleName() + "')"); return false; } else { moduleList.add(module); return true; } } finally { modulesLock.unlock(); } } public static boolean removeModule(Module module) { modulesLock.lock(); try { int index = module.getIndex(); if (modules.remove(module.getId()) == null || index == -1) { Console.printWarning("Core", "removeModule", "Specified module does not exist."); return false; } /* move the last module into the removed module's place */ if (moduleList.isEmpty()) return true; Module last = moduleList.remove(moduleList.size() - 1); module.setIndex(-1); if (index != moduleList.size()) { moduleList.set(index, last); last.setIndex(index); } return true; } finally { modulesLock.unlock(); } } public static boolean saveModules() { modulesLock.lock(); try { boolean success = true; for (int index = 0; index < moduleList.size(); index++) { Module module = moduleList.get(index); if ((module.getIndex() == -1 || module.getPersistentIndex() != module.getIndex()) && !module.save(database, index)) { Console.printError("Core", "saveModules", "Unable to save module '" + module.getModuleName() + "' (id: " + module.getIdString() + ") at position " + index + "."); moduleList.set(index, null); module.setIndex(-1); success = false; } else { moduleList.set(index, module); } } if (database == null) success = false; else success &= database.setMetadata( MODULE_COUNT, Integer.toString(moduleList.size())); return success; } finally { modulesLock.unlock(); } } private static boolean loadQueue(String queue, ArrayList<Task> list) { int taskCount; try { String taskCountString = database.getMetadata(queue + ".count"); if (taskCountString == null) { Console.printError("Core", "loadQueue", "No tasks stored."); return false; } taskCount = Integer.parseInt(taskCountString); } catch (NumberFormatException e) { Console.printError("Core", "loadQueue", "Unable to parse task count."); return false; } list.ensureCapacity(taskCount); for (int i = 0; i < taskCount; i++) { if (list.size() > i && list.get(i) != null) { Console.printError("Core", "loadQueue", "A task already exists at index " + i + "."); continue; } Task task = Task.load(database, queue, i); if (task != null) list.add(task); } return true; } private static boolean saveQueue( boolean isRunning, ArrayList<Task> list) { String queue = getQueueName(isRunning); boolean success = true; for (int index = 0; index < list.size(); index++) { Task task = list.get(index); if (!task.save(database, isRunning, index)) { Module module = task.getModule(); Console.printError("Core", "saveQueue", "Unable to save task (module name: '" + module.getModuleName() + "', id: " + module.getIdString() + ") at position " + index + "."); task.setIndex(-1); list.set(index, null); success = false; } else { list.set(index, task); } } if (database == null) success = false; else success &= database.setMetadata( queue + ".count", Integer.toString(list.size())); return success; } public static boolean loadQueue() { if (database == null) { Console.printError("Core", "loadQueue", "No database available. No tasks loaded."); return false; } tasksLock.lock(); try { boolean success = (loadQueue("running", runningList) && loadQueue("queued", queuedList)); /* dispatch all tasks in the queue */ for (Task task : runningList) { task.setRunning(true); dispatchTask(task); } for (Task task : queuedList) dispatchTask(task); return success; } finally { tasksLock.unlock(); } } public static boolean saveQueue() { if (database == null) { Console.printError("Core", "loadQueue", "No database available. No tasks loaded."); return false; } tasksLock.lock(); try { return (saveQueue(true, runningList) && saveQueue(false, queuedList)); } finally { tasksLock.unlock(); } } public static String getQueueName(boolean isRunning) { return (isRunning ? RUNNING_TASKS : QUEUED_TASKS); } public static Module getModule(long moduleId) { return modules.get(moduleId); } public static List<Module> getModules() { /* to ensure we get a thread-safe snapshot of the current modules */ modulesLock.lock(); try { return new ArrayList<Module>(modules.values()); } finally { modulesLock.unlock(); } } public static int getModuleCount() { return modules.size(); } public static int getTaskCount() { modulesLock.lock(); try { return queuedList.size() + runningList.size(); } finally { modulesLock.unlock(); } } public static List<Task> getQueuedTasks() { /* to ensure we get a thread-safe snapshot of the queued tasks */ tasksLock.lock(); try { return new ArrayList<Task>(queuedList); } finally { tasksLock.unlock(); } } public static List<Task> getRunningTasks() { /* to ensure we get a thread-safe snapshot of the running tasks */ tasksLock.lock(); try { return new ArrayList<Task>(runningList); } finally { tasksLock.unlock(); } } public static Database getDatabase() { return database; } public static Sandbox getSandbox() { return sandbox; } public static void queueTask(Task task) { if (task.isRunning() || task.getIndex() != -1) return; tasksLock.lock(); try { task.setRunning(false); task.setIndex(queuedList.size()); queuedList.add(task); dispatchTask(task); } finally { tasksLock.unlock(); } } public static void startTask(Task task) { if (task.isRunning()) return; tasksLock.lock(); try { int index = task.getIndex(); if (index < 0 || index >= queuedList.size()) return; queuedList.remove(index); task.setRunning(true); task.setIndex(runningList.size()); runningList.add(task); if (!saveQueue()) Console.printError("Core", "startTask", "Unable to save tasks."); } finally { tasksLock.unlock(); } } private static void removeTask(ArrayList<Task> tasks, Task task) { int index = task.getIndex(); if (index < 0 || index >= tasks.size() || tasks.get(index) != task) return; task.setRunning(false); /* move the last module into the removed module's place */ if (tasks.isEmpty()) return; Task last = tasks.remove(tasks.size() - 1); task.setIndex(-1); if (index != tasks.size()) { tasks.set(index, last); last.setIndex(index); } } public static void stopTask(Task task, boolean cancelRescheduling) { tasksLock.lock(); try { if (task.isRunning()) removeTask(runningList, task); else removeTask(queuedList, task); Task.removeTask(task.getId()); /* interrupt the thread if it is running */ ScheduledFuture<Object> thread = task.getFuture(); task.stop(cancelRescheduling); thread.cancel(true); } finally { tasksLock.unlock(); } } private static void dispatchTask(Task task) { long delta = task.getTime() - System.currentTimeMillis(); ScheduledFuture<Object> future = dispatcher.schedule(task, Math.max(delta, 0), TimeUnit.MILLISECONDS); task.setFuture(future); } public static void addPriceRecord(long module, long gid, long price) { Jedis jedis = pool.getResource(); PriceHistory history = PriceHistory.load(jedis.get("history." + toUnsignedString(gid))); if (history == null) history = new PriceHistory(); history.addRecord(module, new Date().getTime(), price); jedis.set("history." + toUnsignedString(gid), history.save()); pool.returnResource(jedis); } public static List<PriceRecord> getPriceHistory(long module, long gid) { Jedis jedis = pool.getResource(); PriceHistory history = PriceHistory.load(jedis.get("history." + toUnsignedString(gid))); pool.returnResource(jedis); if (history == null) return null; return history.getHistory(module); } public static void addPriceTrack(long gid, Long[] modules, Long price) { Jedis jedis = pool.getResource(); PriceTrigger info = PriceTrigger.load(jedis.get("trigger." + toUnsignedString(gid))); if (info == null) info = new PriceTrigger(); if (modules == null) info.addTrack(new PriceTrack(price)); else info.addTrack(new PriceTrack(price), modules); jedis.set("trigger." + toUnsignedString(gid), info.save()); pool.returnResource(jedis); } public static void removePriceTrack(long gid, Long[] modules, Long price) { Jedis jedis = pool.getResource(); PriceTrigger info = PriceTrigger.load(jedis.get("trigger." + toUnsignedString(gid))); if (info == null) return; if (modules == null) info.removeTrack(new PriceTrack(price, 0)); else info.removeTrack(new PriceTrack(price, 0), modules); jedis.set("trigger." + toUnsignedString(gid), info.save()); pool.returnResource(jedis); } public static PriceTrigger getPriceTrigger(long gid) { Jedis jedis = pool.getResource(); PriceTrigger info = PriceTrigger.load(jedis.get("trigger." + toUnsignedString(gid))); pool.returnResource(jedis); return info; } public static boolean checkPrice(Module module, long gid, long price) { Jedis jedis = pool.getResource(); PriceTrigger info = PriceTrigger.load(jedis.get("trigger." + toUnsignedString(gid))); pool.returnResource(jedis); if (info == null) return false; return info.checkPrice(module, price); } public static String priceToString(Long price) { String cents = String.valueOf(price % 100); if (cents.length() == 1) cents = "0" + cents; return "$" + (price / 100) + "." + cents; } public static Long parsePrice(String price) { try { return Long.parseLong(price.replaceAll("\\.", "").replaceAll("\\$", "")); } catch (NumberFormatException e) { return null; } } private static boolean isRunning(String processName) { try { Process p = Runtime.getRuntime().exec("pidof " + processName); BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); String pid = input.readLine(); input.close(); if (pid == null) return false; return true; } catch (IOException e) { Console.printError("Core", "pidof", "", e); return false; } } public static void runCommand(String programName, String command) { try { Process process = Runtime.getRuntime().exec(command); BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = input.readLine(); while (line != null) { Console.println(" " + Console.GRAY + line + Console.DEFAULT); line = input.readLine(); } } catch (IOException e) { Console.printError("Core", "runCommand", "Error starting " + programName + ".", e); } } private static boolean isLocalImage(String path) { File local = new File(IMAGE_PATH + path.substring(IMAGE_WEB_PATH.length())); return path.startsWith(IMAGE_WEB_PATH) && local.exists(); } private static void setImage(long gid, String image) { Results results = database.query(null, new String[] { "entity_id", "module_product_id", "module_id" }, new String[] { "gid" }, new Relation[] { Relation.EQUALS }, new Object[] { gid }, null, null, false, null, null); while (results.next()) { Module module = modules.get(results.getLong(3)); ProductID id = new ProductID(results.getLong(1), results.getString(2)); database.addProductInfo(module, id, new SimpleEntry<String, Object>("image", image)); } } private static void enqueue(long gid, Jedis jedis) { imageQueueLock.lock(); try { BigInteger end = new BigInteger(jedis.get("images.end")); jedis.set("images." + end, toUnsignedString(gid)); jedis.set("images.end", end.add(ONE).toString()); } finally { imageQueueLock.unlock(); } } private static void enqueue(long gid) { Jedis jedis = pool.getResource(); enqueue(gid, jedis); pool.returnResource(jedis); } /** * Gets the fetched image address for the given source image. If the source * image address is on the image cache, this method will return the same * address. If no image is found, the given product ID will be enqueued for * later image fetching. */ public static String getImage(long gid, String image) { if (image == null || !isLocalImage(image)) { /* check to see if image exists */ Jedis jedis = pool.getResource(); String stored = jedis.get("imagestore." + image); if (stored != null) { pool.returnResource(jedis); return stored; } else { stored = jedis.get("imagestore." + gid); if (stored != null) { pool.returnResource(jedis); return stored; } /* queue this product for later image fetching */ enqueue(gid); pool.returnResource(jedis); return null; } } else { return image; } } public static BigInteger getImageQueueStart() { Jedis jedis = pool.getResource(); BigInteger start = new BigInteger(jedis.get("images.start")); pool.returnResource(jedis); return start; } public static BigInteger getImageQueueEnd() { Jedis jedis = pool.getResource(); BigInteger end = new BigInteger(jedis.get("images.end")); pool.returnResource(jedis); return end; } public static void fetchImages(Task task, Module authority) { while (!task.stopped()) { /* get the start and end of the queue */ long gid; Jedis jedis = pool.getResource(); imageQueueLock.lock(); try { Thread.sleep(400); } catch (InterruptedException e) { return; } try { BigInteger start = new BigInteger(jedis.get("images.start")); BigInteger end = new BigInteger(jedis.get("images.end")); if (start.equals(end)) break; gid = new BigInteger(jedis.get("images." + start)).longValue(); } finally { imageQueueLock.unlock(); pool.returnResource(jedis); } Results results = database.query(null, new String[] { "image" }, new String[] { "module_id", "gid" }, new Relation[] { Relation.EQUALS, Relation.EQUALS }, new Object[] { authority.getId(), gid }, null, null, false, null, null); if (results == null) return; /* database became unavailable, likely because we are exiting */ String image = null; while (results.next()) { image = results.getString(1); } if (image == null) { /* the authority does not have an image, so re-add this product to the queue */ jedis = pool.getResource(); imageQueueLock.lock(); try { BigInteger start = new BigInteger(jedis.get("images.start")); BigInteger end = new BigInteger(jedis.get("images.end")); jedis.del("images." + start); jedis.set("images.start", start.add(ONE).toString()); jedis.set("images." + end, toUnsignedString(gid)); jedis.set("images.end", end.add(ONE).toString()); } finally { imageQueueLock.unlock(); pool.returnResource(jedis); } } else if (isLocalImage(image)) { /* the image has already been fetched for this GID, so update all associated products */ setImage(gid, image); /* remove this product from the queue */ jedis = pool.getResource(); imageQueueLock.lock(); try { BigInteger start = new BigInteger(jedis.get("images.start")); jedis.del("images." + start); jedis.set("images.start", start.add(ONE).toString()); jedis.set("imagestore." + gid, image); } finally { imageQueueLock.unlock(); pool.returnResource(jedis); } } else { /* download the image from the authority */ /* ensure that the directory where we wish put the image exists */ boolean error = false; BigInteger bigGid = new BigInteger(toUnsignedString(gid)); File directory = new File(IMAGE_PATH + bigGid.divide(HUNDRED_QUADRILLION)); if (!directory.exists()) { if (!directory.mkdir()) { Console.printError("Core", "fetchImages", "Unable to create directory for cached image."); error = true; } } else if (!directory.isDirectory()) { Console.printError("Core", "fetchImages", "Image cache subdirectory is a file!"); error = true; } /* download the image into the correct directory */ String newPath = null; if (!error) { String extension = image.substring(image.lastIndexOf('.')); String suffix = bigGid.divide(HUNDRED_QUADRILLION) + "/" + bigGid.mod(HUNDRED_QUADRILLION) + extension; String filename = IMAGE_PATH + suffix; newPath = IMAGE_WEB_PATH + suffix; try { ReadableByteChannel rbc = Channels.newChannel(new URL(image).openStream()); FileOutputStream fos = new FileOutputStream(filename); fos.getChannel().transferFrom(rbc, 0, MAX_IMAGE_SIZE); setImage(gid, newPath); } catch (IOException e) { Console.printError("Core", "fetchImages", "Error occurred while fetching image.", e); error = true; } } /* pop the item, and if there is an error, add the product back into the queue */ jedis = pool.getResource(); imageQueueLock.lock(); try { BigInteger start = new BigInteger(jedis.get("images.start")); BigInteger end = new BigInteger(jedis.get("images.end")); jedis.del("images." + start); jedis.set("images.start", start.add(ONE).toString()); if (error) { jedis.set("images." + end, toUnsignedString(gid)); jedis.set("images.end", end.add(ONE).toString()); } else { jedis.set("imagestore." + image, newPath); jedis.set("imagestore." + gid, newPath); } } finally { imageQueueLock.unlock(); pool.returnResource(jedis); } } } } public static void main(String[] args) { AnsiConsole.systemInstall(); /* parse argument flags */ String script = DEFAULT_SCRIPT; for (int i = 0; i < args.length; i++) { if (args[i].equals(SCRIPT_FLAG)) { if (i + 1 < args.length) { i++; script = args[i]; } else { Console.printError("Core", "main", "Unspecified script filepath."); } } else if (args[i].equals(HELP_FLAG)) { /* TODO: implement this */ } else { Console.printError("Core", "main", "Unrecognized flag '" + args[i] + "'."); } } try { database = new MariaDBDriver(); } catch (Exception e) { Console.printError("Core", "main", "Cannot " + "connect to database.", e); } /* check to see if Sphinx is running, and if not, start it */ if (!isRunning(SPHINX_PROCESS)) { Console.lockConsole(); Console.println("Sphinx not running, starting..."); runCommand("Sphinx", SPHINX_COMMAND); Console.unlockConsole(); } /* check to see if Redis is running, and if not, start it */ if (!isRunning(REDIS_PROCESS)) { Console.lockConsole(); Console.println("Redis not running, starting..."); runCommand("Redis", REDIS_COMMAND); Console.unlockConsole(); } /* load random number generator seed */ loadSeed(); /* load the price history/tracking datastore */ JedisPoolConfig config = new JedisPoolConfig(); config.maxActive = THREAD_POOL_SIZE; pool = new JedisPool(config, "localhost"); /* setup the image fetching queue */ Jedis jedis = pool.getResource(); if (jedis.get("images.start") == null) jedis.set("images.start", "0"); if (jedis.get("images.end") == null) jedis.set("images.end", "0"); pool.returnResource(jedis); jedis = pool.getResource(); /* load the script engine */ boolean consoleReady = Console.initConsole(); if (!consoleReady) Console.printError("Core", "main", "Unable to initialize script engine."); /* run the user-specified script */ if (consoleReady && script != null) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(script)); String line; while ((line = reader.readLine()) != null) { if (!Console.parseCommand(line)) return; } } catch (FileNotFoundException e) { Console.printError("Core", "main", "Script file '" + script + "' not found."); } catch (IOException e) { Console.printError("Core", "main", "Error occured" + " while reading script.", e); } finally { try { if (reader != null) reader.close(); } catch (IOException e) { } } } /* find the front-end server */ try { FRONTEND_ADDRESS = InetAddress.getByName("54.244.112.184"); } catch (UnknownHostException e) { Console.printError("Core", "main", "Cannot find front-end server." + " Queries will not be processed."); } /* start the back-end HTTP server */ Connection connection = null; try { connection = new SocketConnection(new ContainerServer(new Server())); connection.connect(new InetSocketAddress(HTTP_SERVER_PORT)); } catch (IOException e) { Console.printError("Core", "main", "Cannot start HTTP server." + " Queries will not be processed.", e); } /* start the main loop */ BackgroundWorker worker = new BackgroundWorker(); ScheduledFuture<?> future = dispatcher.scheduleWithFixedDelay(worker, 0, 10, TimeUnit.SECONDS); if (consoleReady) Console.runConsole(); /* tell all tasks to end */ future.cancel(true); tasksLock.lock(); try { for (Task task : runningList) { if (task != null) { task.stop(true); if (task.getFuture() != null) { task.getFuture().cancel(true); /* wait for task to exit */ try { task.getFuture().get(); } catch (Exception e) { } } } } } finally { tasksLock.unlock(); } dispatcher.shutdown(); /* shutdown the HTTP server */ try { if (connection != null) connection.close(); } catch (IOException e) { Console.printError("Core", "main", "Unable " + "to shutdown HTTP server.", e); } if (database != null) database.close(); } private static class BackgroundWorker implements Runnable { private static final int INDEXER_PERIOD = 100; private static final String INDEXER_COMMAND = "indexer -c index/sphinx.conf --all --rotate"; private int cycles = 0; @Override public void run() { if (cycles % INDEXER_PERIOD == 0) { Console.lockConsole(); Console.println("Regenerating search index..."); runCommand("indexer", INDEXER_COMMAND); Console.unlockConsole(); } if (cycles > 0) saveQueue(); cycles++; } } }