/* * * Panbox - encryption for cloud storage * Copyright (C) 2014-2015 by Fraunhofer SIT and Sirrix AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Additonally, third party code may be provided with notices and open source * licenses from communities and third parties that govern the use of those * portions, and any licenses granted hereunder do not alter any rights and * obligations you may have under such open source licenses, however, the * disclaimer of warranty and limitation of liability provisions of the GPLv3 * will apply to all the product. * */ package org.panbox.desktop.common.sharemgmt; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import org.apache.log4j.Logger; import org.panbox.PanboxConstants; import org.panbox.core.Utils; import org.panbox.core.keymgmt.Volume; import org.panbox.desktop.common.PanboxClient; import org.panbox.desktop.common.gui.shares.PanboxShare; /** * @author palige * * Class implements a NIO {@link WatchService} looking for changes in * the backend directories of the current list of configured shares, * allowing for notification of the user in case of deletion events that * have been triggered outside of Panbox. */ public class ShareWatchService { private final static Logger logger = Logger .getLogger(ShareWatchService.class); private WatchService watchService; private PanboxClient client; private Thread watchServiceThread; private static String devicelistIdentifier; private final static Hashtable<WatchKey, PanboxShare> watchedSharePathList = new Hashtable<WatchKey, PanboxShare>(); public ShareWatchService(PanboxClient client) throws IOException { this.client = client; devicelistIdentifier = Utils.getCertFingerprint( client.getIdentity().getCertSign()).toLowerCase(); this.watchService = FileSystems.getDefault().newWatchService(); this.watchServiceThread = new Thread(new WatchServiceImpl()); } public void start() { this.watchServiceThread.start(); } public synchronized void registerShare(PanboxShare share) { // per default, watch for deletion of .panbox matadata folder try { File shareToWatch = new File(share.getPath()); if (shareToWatch.exists()) { Path p = shareToWatch.toPath(); if (!watchedSharePathList.containsValue(share)) { logger.info("Registering path " + p + " to ShareWatchService"); WatchKey key = p.register(watchService, StandardWatchEventKinds.ENTRY_DELETE); watchedSharePathList.put(key, share); // register .panbox subdirectory, if available File metadataDir = new File(shareToWatch, PanboxConstants.PANBOX_SHARE_METADATA_DIRECTORY); if (metadataDir.exists()) { p = metadataDir.toPath(); logger.info("Registering metadata path " + p + " to ShareWatchService"); key = p.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE); watchedSharePathList.put(key, share); } } else { logger.warn("Path " + p + " already seems to have been registered!"); ; } } } catch (IOException e) { logger.error("Error registering share " + share.getName(), e); } } public synchronized void removeShare(PanboxShare share) { PanboxShare tmp; ArrayList<WatchKey> tmplist = new ArrayList<WatchKey>(); Iterator<Map.Entry<WatchKey, PanboxShare>> it = watchedSharePathList .entrySet().iterator(); // only collect values to remove in first iteration to omit // concurrentmodificationexception while (it.hasNext()) { Map.Entry<WatchKey, PanboxShare> entry = (Map.Entry<WatchKey, PanboxShare>) it .next(); if (entry.getValue().equals(share)) { tmplist.add(entry.getKey()); } } for (int i = 0; i < tmplist.size(); i++) { if ((tmp = watchedSharePathList.remove(tmplist.get(i))) == null) { logger.error("Error deleting path from list of watched paths: " + share.getPath()); } else { ((WatchKey) tmplist.get(i)).cancel(); logger.info("Removed path " + tmp.getPath() + " from ShareWatchService"); } } } private class WatchServiceImpl implements Runnable { @Override public void run() { try { WatchKey key = watchService.take(); while (key != null) { for (WatchEvent<?> event : key.pollEvents()) { logger.info("Received " + event.kind() + " event for file: " + event.context()); if (event.kind() == StandardWatchEventKinds.OVERFLOW) { continue; } else { if ((event.kind() == StandardWatchEventKinds.ENTRY_DELETE) && (event.context() instanceof Path)) { Path path = (Path) event.context(); if (path.toString() .equals(PanboxConstants.PANBOX_SHARE_METADATA_DIRECTORY)) { // panbox share metadata dir was deleted -> // trigger share removal try { PanboxShare share = watchedSharePathList .get(key); if (share != null) { client.removeShare(share); key.cancel(); } } catch (Exception e) { logger.error( "Removing share from DB failed!", e); } } } else if ((event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) && (event.context() instanceof Path)) { Path path = (Path) event.context(); if (path.toString().equalsIgnoreCase( Volume.SPL_FILE) || path.toString().equalsIgnoreCase( devicelistIdentifier)) { logger.info("Detected modification in share metadata file " + path.toString() + ". Initiating metadata refresh"); try { PanboxShare share = watchedSharePathList .get(key); if (share != null) { PanboxShare newShare = client .reloadShare(share); removeShare(share); registerShare(newShare); } } catch (Exception e) { logger.error("Reloading share failed!", e); } } } else if ((event.kind() == StandardWatchEventKinds.ENTRY_CREATE) && (event.context() instanceof Path)) { Path path = (Path) event.context(); String chk = path.toString().toLowerCase(); if (chk.startsWith(devicelistIdentifier) && (chk.length() > (devicelistIdentifier .length() + ".db".length())) && !chk.contains("journal")) { logger.info("Detected creation of potential conflicting copy in in device list file. Name: " + path.toString() + ". Initiating metadata refresh"); try { PanboxShare share = watchedSharePathList .get(key); if (share != null) { client.conflictNotification(share, chk); } } catch (Exception e) { logger.error("Reloading share failed!", e); } } } } } key.reset(); key = watchService.take(); } } catch (InterruptedException e) { logger.warn("ShareWatchService interrupted.", e); } } } }