/* * This file is part of the Wayback archival access software * (http://archive-access.sourceforge.net/projects/wayback/). * * Licensed to the Internet Archive (IA) by one or more individual * contributors. * * The IA licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.archive.wayback.resourcestore.locationdb; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.logging.Logger; import org.archive.wayback.Shutdownable; import org.archive.wayback.resourcestore.resourcefile.ResourceFileList; import org.archive.wayback.resourcestore.resourcefile.ResourceFileLocation; import org.archive.wayback.util.DirMaker; /** * Class which performs updates on a ResourceFileLocationDB, based on files * appearing in a incoming directory. When files are noticed in the "incoming" * directory, they are assumed to be in the format serialized by * org.archive.wayback.resourcestore.resourcefile.ResourceFileList * * These files are synchronized with the ResourceFileLocationDB, and deleted. * * Each file has a logical name, which is assumed to uniquely identify a * ResourceFileSource. As an optimization, the last state of each * ResouceFileSource is kept in a file under the "state" directory. * * This allows this class to compute a difference of the last state with the * new files in incoming, and only deltas: new files, removed files, * and possibly moved files, need to applied to the ResourceFileLocationDB. * * @author brad * @version $Date$, $Revision$ */ public class ResourceFileLocationDBUpdater implements Shutdownable { private static final Logger LOGGER = Logger.getLogger(ResourceFileLocationDBUpdater.class.getName()); private ResourceFileLocationDB db = null; private File stateDir = null; private File incomingDir = null; private UpdateThread thread = null; private long interval = 120000; public final static String TMP_SUFFIX = ".TMP"; public void init() { if(interval > 0) { thread = new UpdateThread(this,interval); thread.start(); } } public void shutdown() { if(thread != null) { thread.interrupt(); try { thread.join(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public int synchronizeIncoming() throws IOException { File[] updates = incomingDir.listFiles(); int updated = 0; for(File update : updates) { if(update.getName().endsWith(TMP_SUFFIX)) { continue; } if(synchronize(update)) { updated++; } } return updated; } public boolean synchronize(File update) throws IOException { String name = update.getName(); File current = new File(stateDir,name); if(!current.isFile()) { current.createNewFile(); } ResourceFileList updateFL = ResourceFileList.load(update); ResourceFileList currentFL = ResourceFileList.load(current); boolean updated = false; ResourceFileList removedFiles = currentFL.subtract(updateFL); ResourceFileList addedFiles = updateFL.subtract(currentFL); Iterator<ResourceFileLocation> addedItr = addedFiles.iterator(); Iterator<ResourceFileLocation> removedItr = removedFiles.iterator(); while(addedItr.hasNext()) { updated = true; ResourceFileLocation location = addedItr.next(); LOGGER.info("Added " + location.getName() + " " + location.getUrl()); db.addNameUrl(location.getName(), location.getUrl()); } while(removedItr.hasNext()) { updated = true; ResourceFileLocation location = removedItr.next(); LOGGER.info("Removed " + location.getName() + " " + location.getUrl()); db.removeNameUrl(location.getName(), location.getUrl()); } if(updated) { // lastly replace the state file with the new version: if(!current.delete()) { throw new IOException("Unable to delete " + current.getAbsolutePath()); } if(!update.renameTo(current)) { throw new IOException("Unable to rename " + update.getAbsolutePath() + " to " + current.getAbsolutePath()); } } else { if(!update.delete()) { throw new IOException("Unable to delete " + update.getAbsolutePath()); } } return updated; } private class UpdateThread extends Thread { private long runInterval = 120000; private ResourceFileLocationDBUpdater updater = null; public UpdateThread(ResourceFileLocationDBUpdater updater, long runInterval) { this.updater = updater; this.runInterval = runInterval; } public void run() { LOGGER.info("ResourceFileLocationDBUpdater.UpdateThread is alive."); long sleepInterval = runInterval; while (true) { try { int updated = updater.synchronizeIncoming(); if(updated > 0) { sleepInterval = runInterval; } else { sleepInterval += runInterval; } sleep(sleepInterval); } catch (InterruptedException e) { LOGGER.info("Shutting Down."); return; } catch (IOException e) { e.printStackTrace(); } } } } /** * @return the db */ public ResourceFileLocationDB getDb() { return db; } /** * @param db the db to set */ public void setDb(ResourceFileLocationDB db) { this.db = db; } /** * @return the stateDir */ public String getStateDir() { return DirMaker.getAbsolutePath(stateDir); } /** * @param stateDir the stateDir to set * @throws IOException */ public void setStateDir(String stateDir) throws IOException { this.stateDir = DirMaker.ensureDir(stateDir); } /** * @return the incomingDir */ public String getIncomingDir() { return DirMaker.getAbsolutePath(incomingDir); } /** * @param incomingDir the incomingDir to set * @throws IOException */ public void setIncomingDir(String incomingDir) throws IOException { this.incomingDir = DirMaker.ensureDir(incomingDir); } /** * @return the interval */ public long getInterval() { return interval; } /** * @param interval the interval to set */ public void setInterval(long interval) { this.interval = interval; } }