/* * Part of the CCNx Java Library. * * Copyright (C) 2012, 2013 Palo Alto Research Center, Inc. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. You should have received * a copy of the GNU Lesser General Public License along with this library; * if not, write to the Free Software Foundation, Inc., 51 Franklin Street, * Fifth Floor, Boston, MA 02110-1301 USA. */ package org.ccnx.ccn.impl.sync; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.ArrayList; import org.ccnx.ccn.CCNSyncHandler; import org.ccnx.ccn.config.ConfigurationException; import org.ccnx.ccn.config.SystemConfiguration; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.ConfigSlice; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.MalformedContentNameStringException; public class FileBasedSyncMonitor extends SyncMonitor implements Runnable{ //this is a temporary implementation allowing the java library to fake handling of sync control traffic to glean new names from the repository. //This implementation temporarily will utilize bash commands to make and copy backend repository files //This implementation will use a thread from the library threadpool for processing when callbacks are registered. int runInterval = 2000; private static Object runningLock = new Object(); private static Boolean isRunning = false; private static boolean shutDown = false; private static FileChannel fileChannel; private static String filename; public FileBasedSyncMonitor() throws ConfigurationException{ synchronized (runningLock) { if (!isRunning) { //based on the env variable for running ccnr filename = System.getenv("CCNR_DIRECTORY"); if (filename == null) { Log.severe("Please set CCNR_DIRECTORY environment variable before running!"); throw new ConfigurationException("Please set CCNR_DIRECTORY environment variable before running!"); } String configError = "Please install the CCNx installation or set your path to use the ccnnamelist command"; try { Log.fine(Log.FAC_SYNC, "PATH: {0}", System.getenv("PATH")); ProcessBuilder pbCheck = new ProcessBuilder(); pbCheck.command("/bin/sh", "-c", "command -v ccnnamelist"); int exitStatus = pbCheck.start().waitFor(); if (exitStatus == 0) { //the command is found! Log.fine(Log.FAC_SYNC, "ccnnamelist command was found in the path!"); } else { Log.severe(configError); throw new ConfigurationException(configError); } } catch (IOException e2) { Log.severe("{0} Exception while checking for correct setup: {1}", configError, e2.getMessage()); throw new ConfigurationException(configError); } catch (InterruptedException e) { Log.severe("{0} Exception while checking for correct setup: {1}", configError, e.getMessage()); throw new ConfigurationException(configError); } } else { Log.fine(Log.FAC_SYNC, "no need to check config! SyncMonitor is already running!"); } } } /** * Kick off the worker thread to monitor differences in the backend file * * First check if there are even differences in the timestamps */ public void run() { boolean keepRunning = true; synchronized(runningLock) { isRunning = true; } File repoDir = new File(filename); Log.fine(Log.FAC_SYNC, "repoDir: "+repoDir); File repoFile = new File(filename+"/repoFile1"); String commandCreateDiff= "ccnnamelist repoFile1 > names ; sort names > newnames ; rm names ; :>> oldnames ; diff newnames oldnames > diffNames ; mv newnames oldnames"; String commandCreateDiffFinal; long lastReadTime = -1; long repoFileTime; long lastModified = -1; FileLock fileLock = null; while (keepRunning) { try { fileChannel = new RandomAccessFile(filename+"/sync.lock", "rw").getChannel(); fileLock = fileChannel.lock(); } catch (FileNotFoundException e1) { Log.severe("CCNR_DIRECTORY setting = {0}: file not found. Exiting.", filename); System.exit(1); } catch (IOException e) { try { fileLock.release(); } catch (IOException e1) { //already in an error state... Log.warning("Error releasing lock for Sync API file processing: {0}", e1.getMessage()); } Log.severe("Exception when trying to acquire lock to process new names for Sync API: {0}. Exiting.", e.getMessage()); System.exit(1); } repoFileTime = repoFile.lastModified(); //System.out.println("on a new loop! repoFileTime = "+repoFileTime+" lastModified = "+lastModified+" lastReadTime = "+lastReadTime); //is the diff file new to me? for (String diffFiles : repoDir.list()) { if (diffFiles.startsWith("diffNames.")) { //this is a diff names to process Log.fine(Log.FAC_SYNC, "have a diffFile: "+diffFiles); if (lastModified < Long.parseLong(diffFiles.substring(10)) || lastModified == -1) { //we need to read in this file Log.fine(Log.FAC_SYNC, "this is a new diffFile! reading it in!"); lastModified = Long.parseLong(diffFiles.substring(10)); //read in file here try { Log.fine(Log.FAC_SYNC, "going to read in: "+filename+"/"+diffFiles); BufferedReader buf = new BufferedReader(new FileReader(filename+"/"+diffFiles)); String line = buf.readLine(); while ( line != null ) { Log.fine(Log.FAC_SYNC, "reading in line!: {0}", line); processNewName(line); line = buf.readLine(); } lastReadTime = lastModified; } catch (IOException e) { Log.warning("Error while executing bash commands to find diffs in repo files: {0}", e.getMessage()); } lastReadTime = lastModified; } else { //System.out.println("i already read in this file."); } } } Log.fine(Log.FAC_SYNC, "checking if the repo backend is new. last modified: {0} repoFileTime: {1} diff: {2} runInterval: {3}", lastModified, repoFileTime, (System.currentTimeMillis() - lastModified), runInterval); //now check if the file is even new... might have more names to process if (lastModified == -1 || (repoFileTime > lastModified && System.currentTimeMillis() - lastModified > runInterval )) { Log.fine(Log.FAC_SYNC, "the repo has a new backend, and the last time the process was run at least one runInterval before"); lastModified = repoFileTime; //there is something new in the backend file... need to process Process pr; try { commandCreateDiffFinal = commandCreateDiff.replace("diffNames", "diffNames."+repoFileTime); if (lastReadTime!=-1) commandCreateDiffFinal = commandCreateDiffFinal.concat(" ; rm diffNames."+lastReadTime); Log.fine(Log.FAC_SYNC, "executing command: "+commandCreateDiffFinal); ProcessBuilder pb = new ProcessBuilder(); pb.directory(repoDir); pb.redirectErrorStream(true); pb.command("/bin/sh", "-c", commandCreateDiffFinal); pr = pb.start(); pr.waitFor(); Log.fine(Log.FAC_SYNC, "done processing the backend, now reading in diffs: "+filename+"/diffNames."+repoFileTime); BufferedReader buf = new BufferedReader(new FileReader(filename+"/diffNames."+repoFileTime)); String line = buf.readLine(); while ( line != null ) { Log.fine(Log.FAC_SYNC, "reading in line!: {0}", line); processNewName(line); line = buf.readLine(); } lastReadTime = lastModified; } catch (IOException e) { Log.warning("Error while executing bash commands to find diffs in repo files: {0}", e.getMessage()); } catch (InterruptedException e) { Log.warning("Error while waiting for bash commands to find diffs in repo files: {0}", e.getMessage()); } } else { //there is nothing new, might as well sleep //System.out.println("nothing new at this time... going back to sleep"); } try { fileLock.release(); } catch (IOException e) { Log.severe("Exception when trying to release lock to process new names for Sync API: {0}. Exiting.", e.getMessage()); System.exit(1); } try { Thread.sleep(runInterval); } catch (InterruptedException e) { Log.warning("FileBasedSyncMonitor: error while sleeping... {0}", e.getMessage()); } synchronized(runningLock) { if (checkShutdown()) { keepRunning = false; Log.fine(Log.FAC_SYNC, "isRunning was false, time to shut down"); isRunning = false; shutDown = false; } } } return; } private void processNewName(String line) { line = line.replaceAll("< ", ""); if (line.startsWith("ccnx:/")) { try { ContentName newName = ContentName.fromURI(line); synchronized(callbacks) { for (ConfigSlice cs : callbacks.keySet()) { if (cs.prefix.isPrefixOf(newName)) { for (CCNSyncHandler handler: callbacks.get(cs)) { handler.handleContentName(cs, newName); } } } } } catch (MalformedContentNameStringException e) { Log.warning("Error while processing new names from executed script: {0} {1}", line, e.getMessage()); } } } private boolean checkShutdown() { synchronized(runningLock) { return shutDown; } } private void processCallback(CCNSyncHandler syncHandler, ConfigSlice slice) { synchronized(callbacks) { Log.fine(Log.FAC_SYNC, "isRunning: {0}", isRunning); synchronized (runningLock) { if (!isRunning && !checkShutdown()) { //this means we do not have a thread... and therefore are not watching the names now. Log.fine(Log.FAC_SYNC, "sync was not running, clearing callbacks and starting up sync thread"); shutDown = false; callbacks.clear(); //System.out.println("was not running... starting up now!"); SystemConfiguration._systemThreadpool.execute(this); } else if (isRunning && checkShutdown()) { //in case we thought we were done running, but really need to stay up. Log.fine(Log.FAC_SYNC, "sync still running, but was set to shutdown, cancel shutdown and clear callbacks"); shutDown = false; callbacks.clear(); } else if (isRunning && !checkShutdown()) { //normal operation... good. Log.fine(Log.FAC_SYNC, "sync in normal operation during callback"); } else { Log.fine(Log.FAC_SYNC, "invalid run state: isRunning = {0} checkShutdown = {1}", isRunning, checkShutdown()); } } //now we need to register the callback... check the prefix registerCallbackInternal(syncHandler, slice); } } private void processRemoveCallback(CCNSyncHandler syncHandler, ConfigSlice slice) { synchronized(callbacks) { removeCallbackInternal(syncHandler, slice); if (callbacks.isEmpty()) { Log.fine(Log.FAC_SYNC, "all callbacks are removed, shutting down sync"); //we don't have any registered callbacks, we can go ahead and stop running. synchronized (runningLock) { shutDown = true; } } } } public void registerCallback(CCNSyncHandler syncHandler, ConfigSlice slice, byte[] startHash, ContentName startName) { //start monitoring the namespace, if not already monitored... if so, add to the callback list processCallback(syncHandler, slice); } public void removeCallback(CCNSyncHandler syncHandler, ConfigSlice slice) { // remove callback from hashmap. turn off thread if no longer needed processRemoveCallback(syncHandler, slice); } public void shutdown(ConfigSlice slice) { ArrayList<CCNSyncHandler> cb = callbacks.get(slice); // Will automatically shutdown when all are removed for (CCNSyncHandler syncHandler : cb) removeCallback(syncHandler, slice); } public SyncNodeCache getNodeCache(ConfigSlice slice) { return null; } }