/* * CMISBox - Synchronize and share your files with your CMIS Repository * * Copyright (C) 2011 - Andrea Agili * * CMISBox 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. * * CMISBox 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 CMISBox. If not, see <http://www.gnu.org/licenses/>. * */ package com.github.cmisbox.core; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.DelayQueue; import java.util.regex.Pattern; import org.apache.chemistry.opencmis.client.api.CmisObject; import org.apache.chemistry.opencmis.client.api.Document; import org.apache.chemistry.opencmis.client.api.Folder; import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.github.cmisbox.persistence.Storage; import com.github.cmisbox.persistence.StoredItem; import com.github.cmisbox.remote.CMISRepository; import com.github.cmisbox.remote.ChangeItem; import com.github.cmisbox.remote.Changes; import com.github.cmisbox.ui.UI; import com.github.cmisbox.ui.UI.Status; public class Queue implements Runnable { private static Queue instance = new Queue(); public static Queue getInstance() { return Queue.instance; } private boolean active = true; // by default do not synch files starting with a dot private Pattern filter = Pattern.compile("^\\..*"); private Thread thread; private DelayQueue<LocalEvent> delayQueue = new DelayQueue<LocalEvent>(); private Log log; private Queue() { this.thread = new Thread(this, "Queue"); this.thread.start(); this.log = LogFactory.getLog(this.getClass()); } public synchronized void add(LocalEvent localEvent) { this.log.debug("Asked to queue" + localEvent); if (!this.active) { return; } if ((localEvent.getName() != null) && this.filter.pattern().matches(localEvent.getName())) { this.log.debug("Filtered " + localEvent); return; } if (this.delayQueue.contains(localEvent) || localEvent.isRename()) { Iterator<LocalEvent> i = this.delayQueue.iterator(); while (i.hasNext()) { LocalEvent queuedEvent = i.next(); if (queuedEvent.equals(localEvent)) { localEvent.merge(queuedEvent); i.remove(); this.log.debug("" + "Merged " + queuedEvent); } else if (Config.getInstance().isMacOSX() && localEvent.isRename() && queuedEvent.isDelete()) { if (localEvent.isParent(queuedEvent)) { i.remove(); } } } } if (!(localEvent.isCreate() && localEvent.isDelete())) { this.delayQueue.put(localEvent); this.log.debug("Queued " + localEvent); } } public Pattern getFilter() { return this.filter; } private StoredItem getSingleItem(String path) throws Exception { List<StoredItem> itemList = Storage.getInstance().findByPath(path); if (itemList.size() == 1) { return itemList.get(0); } else { throw new Exception(String.format( "Expected one result in index: %s -> %s", path, itemList)); } } public void manageEvent(LocalEvent event) { Log log = LogFactory.getLog(this.getClass()); log.debug("managing: " + event); // any platform // - a folder can be renamed before containing files are managed: on // folder rename all children must be updated while still in queue; // linux // - if a file or folder is moved out of a watched folder it is reported // as a rename to null (check if it's still there) // mac osx // - recursive folder operations (e.g. unzip an archive or move a folder // inside a watched folder) are not reported, only root folder is // reported as create // - folder rename causes children to be notified as deleted (with old // path) try { if (event.isSynch()) { this.synchAllWatches(); return; } File f = new File(event.getFullFilename()); if (event.isCreate()) { StoredItem item = this.getSingleItem(event.getLocalPath()); if ((item != null) && (item.getLocalModified().longValue() >= f .lastModified())) { return; } String parent = f.getParent().substring( Config.getInstance().getWatchParent().length()); CmisObject obj = CMISRepository.getInstance().addChild( this.getSingleItem(parent).getId(), f); Storage.getInstance().add(f, obj); } else if (event.isDelete()) { StoredItem item = this.getSingleItem(event.getLocalPath()); if (f.exists()) { throw new Exception(String.format( "File %s reported to be deleted but stil exists", f.getAbsolutePath())); } CMISRepository.getInstance().delete(item.getId()); Storage.getInstance().delete(item, true); } else if (event.isModify()) { if (f.isFile()) { StoredItem item = this.getSingleItem(event.getLocalPath()); if (item.getLocalModified().longValue() < f.lastModified()) { Document doc = CMISRepository.getInstance().update( item, f); Storage.getInstance().localUpdate(item, f, doc); } else { log.debug("file" + f + " modified in the past"); } } } else if (event.isRename()) { StoredItem item = this.getSingleItem(event.getLocalPath()); CmisObject obj = CMISRepository.getInstance().rename( item.getId(), f); Storage.getInstance().localUpdate(item, f, obj); } } catch (Exception e) { log.error(e); if (log.isDebugEnabled()) { e.printStackTrace(); } if (UI.getInstance().isAvailable()) { UI.getInstance().notify(e.toString()); UI.getInstance().setStatus(Status.KO); } } } private String resolvePath(Folder parent) throws Exception { StoredItem item = Storage.getInstance().findById(parent.getId()); String path = File.separator + parent.getName(); Folder ancestor = parent.getFolderParent(); while (item == null) { if (ancestor == null) { return null; } item = Storage.getInstance().findById(ancestor.getId()); if (item == null) { path = File.separator + ancestor.getName() + path; } else { path = item.getPath() + path; } ancestor = ancestor.getFolderParent(); } return Config.getInstance().getWatchParent() + path; } public void run() { while (this.active) { try { this.manageEvent(this.delayQueue.take()); if (this.delayQueue.isEmpty()) { } } catch (InterruptedException e) { LogFactory.getLog(this.getClass()).info(this, e); } } } public void setFilter(Pattern filter) { this.filter = filter; } public void stop() { this.active = false; this.delayQueue.clear(); this.thread.interrupt(); } private void synchAllWatches() throws Exception { Storage storage = Storage.getInstance(); CMISRepository cmisRepository = CMISRepository.getInstance(); UI ui = UI.getInstance(); Config config = Config.getInstance(); if (ui.isAvailable()) { ui.setStatus(Status.SYNCH); } List<String[]> updates = new ArrayList<String[]>(); Changes changes = cmisRepository .getContentChanges(storage.getRootIds()); LinkedHashMap<String, File> downloadList = new LinkedHashMap<String, File>(); boolean errors = false; for (ChangeItem item : changes.getEvents()) { try { if (item == null) { continue; } String id = "workspace://SpacesStore/" + item.getId(); String type = item.getT(); StoredItem storedItem = storage.findById(id); if (type.equals("D")) { if (storedItem != null) { File f = new File(config.getWatchParent() + storedItem.getPath()); f.delete(); storage.delete(storedItem, true); } } else if (type.equals("C") || type.equals("U")) { CmisObject remoteObject = cmisRepository.findObject(id); remoteObject.refresh(); if (remoteObject.getType().getBaseTypeId() .equals(BaseTypeId.CMIS_FOLDER)) { Folder folder = (Folder) remoteObject; File newFile = new File(this.resolvePath(folder .getFolderParent()), folder.getName()); if (storedItem == null) { storage.add(newFile, folder, false); } else { if ((folder.getLastModificationDate() .getTimeInMillis() > storedItem .getRemoteModified()) && !storedItem.getName().equals( folder.getName())) { if (new File(storedItem.getAbsolutePath()) .renameTo(newFile)) { storage.localUpdate(storedItem, newFile, folder); } else { if (ui.isAvailable()) { ui.notify(Messages.renameError + " " + storedItem.getAbsolutePath() + " -> " + newFile.getAbsolutePath()); } this.log.error("Unable to rename " + storedItem.getAbsolutePath() + " to " + newFile.getAbsolutePath()); } } } } else { Document document = (Document) remoteObject; this.log.debug("preparing to update or create " + document.getName()); File newFile = new File(this.resolvePath(document .getParents().get(0)), document.getName()); if (storedItem == null) { downloadList.put(id, newFile); } else { File current = new File( storedItem.getAbsolutePath()); if (storedItem.getLocalModified() < document .getLastModificationDate() .getTimeInMillis()) { if (!current.getAbsolutePath().equals( newFile.getAbsolutePath())) { if (!current.renameTo(newFile)) { if (ui.isAvailable()) { ui.notify(Messages.renameError + " " + storedItem .getAbsolutePath() + " -> " + newFile.getAbsolutePath()); } this.log.error("Unable to rename " + storedItem.getAbsolutePath() + " to " + newFile.getAbsolutePath()); } } downloadList.put(id, newFile); } } } } } catch (Exception e1) { errors = true; this.log.error("Error getting remote chahges for " + item, e1); } } if (ui.isAvailable() && (downloadList.size() > 0)) { ui.notify(Messages.downloading + " " + downloadList.size() + " " + Messages.files); } for (Entry<String, File> e : downloadList.entrySet()) { try { storage.deleteById(e.getKey()); e.getValue().delete(); cmisRepository.download(cmisRepository.getDocument(e.getKey()), e.getValue()); storage.add(e.getValue(), cmisRepository.getDocument(e.getKey())); } catch (Exception e1) { errors = true; this.log.error("Error downloading " + e, e1); if (ui.isAvailable()) { ui.notify(Messages.errorDownloading + " " + e); } } } if (!errors) { config.setChangeLogToken(changes.getToken()); } if (ui.isAvailable()) { ui.setStatus(Status.OK); if (updates.size() == 1) { ui.notify(updates.get(0)[0] + " " + Messages.updatedBy + " " + updates.get(0)[1]); } else if (updates.size() > 1) { ui.notify(Messages.updated + " " + updates.size() + Messages.files); } } } }