package org.docear.plugin.services.features.upload; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import javax.swing.SwingUtilities; import org.docear.plugin.core.DocearController; import org.docear.plugin.core.event.DocearEvent; import org.docear.plugin.core.event.DocearEventType; import org.docear.plugin.core.event.IDocearEventListener; import org.docear.plugin.core.features.DocearMapModelExtension; import org.docear.plugin.core.io.DirectoryObserver; import org.docear.plugin.core.logging.DocearLogger; import org.docear.plugin.services.ADocearServiceFeature; import org.docear.plugin.services.ServiceController; import org.docear.plugin.services.features.user.DocearUser; import org.freeplane.core.resources.ResourceController; import org.freeplane.core.user.IUserAccountChangeListener; import org.freeplane.core.user.UserAccountChangeEvent; import org.freeplane.core.user.UserAccountController; import org.freeplane.core.util.LogUtils; import org.freeplane.features.map.MapModel; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; import org.freeplane.plugin.workspace.URIUtils; public class UploadController extends ADocearServiceFeature { private static FileFilter zipFilter = new FileFilter() { public boolean accept(File f) { return (f != null && f.getName().toLowerCase().endsWith(".zip")); } }; private final Map<String, MapModel> mapUploadQueue = new HashMap<String, MapModel>(); private final Set<DirectoryObserver> observers = new HashSet<DirectoryObserver>(); private final Runnable packerRunner = new Runnable() { public void run() { createPackages(); } }; private final CyclicUploadPacker packerThread = new CyclicUploadPacker(packerRunner, (180)); //every 3 minutes private final UploadThread uploadThread = new UploadThread(this); private final Set<File> uploadFiles = new HashSet<File>(); private final DirectoryObserver defaultObserver = new DirectoryObserver() { public void fileRemoved(File file) { uploadFiles.remove(file); } public void fileCreated(File file) { uploadFiles.add(file); } }; public UploadController() { DocearController.getController().getEventQueue().addEventListener(new IDocearEventListener() { public void handleEvent(DocearEvent event) { if (event.getType() == DocearEventType.APPLICATION_CLOSING) { shutdown(); } else if (event.getType() == DocearEventType.FINISH_THREADS) { finishThreads(); } } }); this.addUploadDirectoryObserver(defaultObserver); } /** * @return */ public boolean isBackupEnabled() { DocearUser userSettings = ServiceController.getCurrentUser(); return userSettings.isBackupEnabled() && userSettings.isTransmissionEnabled() && userSettings.isValid(); } public boolean isUploadEnabled() { DocearUser user = ServiceController.getCurrentUser(); boolean needUser = (user.isBackupEnabled() || user.isRecommendationsEnabled()) && user.isTransmissionEnabled() && user.isOnline(); return needUser && user.isValid(); } /** * Provides the time in minutes until the next upload cycle should be started * * @return time to wait until the next upload cycle */ public int getUploadInterval() { final ResourceController resourceCtrl = Controller.getCurrentController().getResourceController(); int backupMinutes = resourceCtrl.getIntProperty("save_backup_automcatically", 0); if (backupMinutes <= 0) { backupMinutes = 30; } return backupMinutes; } /** * @return */ public File getUploadDirectory(String forName) { File uploadBufferDirectory = new File(getUploadBufferPath(), forName); if (!uploadBufferDirectory.exists()) { uploadBufferDirectory.mkdirs(); } return uploadBufferDirectory; } public File getUploadBufferPath() { return new File(URIUtils.getFile(ServiceController.getController().getUserSettingsHome()), "queue"); } /** * @return */ public File[] getUploadPackages(String forName) { return getUploadDirectory(forName).listFiles(zipFilter); } public Iterator<File> getUploadJobs() { return new Iterator<File>() { int i = 0; File[] files = uploadFiles.toArray(new File[0]); public boolean hasNext() { return i < files.length; } public File next() { return files[i++]; } public void remove() { synchronized (uploadFiles) { uploadFiles.remove(files[i-1]); } } }; } public void refreshUploadBuffer() { File[] files = getUploadPackages("mindmaps"); if(files == null) { return; } synchronized (uploadFiles) { for(File file : files) { try { new ZipFile(file); uploadFiles.add(file); } catch(Exception e) { LogUtils.warn("org.docear.plugin.services.features.upload.UploadController.refreshUploadBuffer() -> corrupted ZipFile: "+file.getAbsolutePath()); file.delete(); } } } } /** * @param map */ public void addMapToUpload(MapModel map) { boolean backup = isBackupEnabled(); File file = map.getFile(); if(file == null || (!backup && !isUploadEnabled())) { return; } DocearMapModelExtension mapExt = map.getExtension(DocearMapModelExtension.class); if(mapExt != null && mapExt.getMapId() != null) { synchronized (mapUploadQueue) { mapUploadQueue.put(mapExt.getMapId(), map); } } } /** * @param observer */ public void addUploadDirectoryObserver(DirectoryObserver observer) { synchronized (observers) { observers.add(observer); } } /** * @param observer */ public void removeUploadDirectoryObserver(DirectoryObserver observer) { synchronized (observers) { observers.remove(observer); } } /** * */ private void createPackages() { synchronized (mapUploadQueue) { Iterator<Entry<String, MapModel>> iter = mapUploadQueue.entrySet().iterator(); while(iter.hasNext()) { Entry<String, MapModel> entry = iter.next(); createMapPackage(entry.getValue()); iter.remove(); } } } /** * @param file */ protected final void fireFileCreated(File file) { synchronized (observers) { for(DirectoryObserver observer : observers) { observer.fileCreated(file); } } } /** * @param file */ protected final void fireFileRemoved(File file) { synchronized (observers) { for(DirectoryObserver observer : observers) { observer.fileRemoved(file); } } } /** * @param map */ private void createMapPackage(final MapModel map) { if (map == null) { return; } final Properties meta = getMapProperties(map); if (meta == null) { return; } Thread thread = new Thread() { public void run() { try { File backupFile = new File(getUploadDirectory("mindmaps").getAbsolutePath(), System.currentTimeMillis() + "_" + map.getFile().getName() + ".zip"); FileOutputStream fout = null; ZipOutputStream out = null; InputStream in = null; try { fout = new FileOutputStream(backupFile); out = new ZipOutputStream(fout); in = new FileInputStream(map.getFile()); ZipEntry entry = new ZipEntry("metadata.inf"); out.putNextEntry(entry); meta.store(out, ""); entry = new ZipEntry(map.getFile().getName()); out.putNextEntry(entry); while (true) { int data = in.read(); if (data == -1) { break; } out.write(data); } out.flush(); } finally { in.close(); out.close(); fout.close(); fireFileCreated(backupFile); DocearController.getController().removeWorkingThreadHandle(this.getName()); } } catch (Exception e) { DocearLogger.warn("org.docear.plugin.services.upload.UploadController.createMapPackage(): "+e.getMessage()); } } }; DocearController.getController().addWorkingThreadHandle(thread.getName()); thread.start(); } /** * @param map * @return */ private Properties getMapProperties(MapModel map) { DocearUser userSettings = ServiceController.getCurrentUser(); DocearController docearController = DocearController.getController(); DocearMapModelExtension dmme = map.getExtension(DocearMapModelExtension.class); if (dmme == null) { return null; } boolean isLibraryMap = DocearController.getController().isLibraryMap(map); String typeName = (dmme.getType() == null ? "" : dmme.getType().name()); Properties properties = new Properties(); properties.put("mindmap_id", dmme.getMapId()); properties.put("timestamp", ""+System.currentTimeMillis()); properties.put("is_library_map", new Boolean(isLibraryMap).toString()); properties.put("backup", new Boolean(userSettings.isBackupEnabled()).toString()); properties.put("allow_content_research", new Boolean(false).toString()); properties.put("allow_information_retrieval", new Boolean(false).toString()); properties.put("allow_usage_research", new Boolean(false).toString()); properties.put("allow_recommendations", new Boolean(userSettings.isRecommendationsEnabled()).toString()); properties.put("enable_synchronization", new Boolean(userSettings.isSynchronizationEnabled()).toString()); properties.put("enable_collaboration", new Boolean(userSettings.isCollaborationEnabled()).toString()); if (typeName != null && typeName.trim().length()>0) { properties.put("map_type", typeName); } properties.put("map_version", dmme.getVersion()); properties.put("application_name", docearController.getApplicationName()); properties.put("application_version", docearController.getApplicationVersion()); properties.put("application_status", docearController.getApplicationStatus()); properties.put("application_status_version", docearController.getApplicationStatusVersion()); properties.put("application_build", ""+docearController.getApplicationBuildNumber()); properties.put("application_date", docearController.getApplicationBuildDate()); properties.put("filesize", ""+map.getFile().length()); properties.put("filename", map.getFile().getName()); properties.put("filepath", map.getFile().getAbsolutePath()); return properties; } /** * */ public void finishThreads() { String runnerID = Integer.toHexString(this.hashCode()); DocearController.getController().addWorkingThreadHandle(runnerID); packerRunner.run(); DocearController.getController().removeWorkingThreadHandle(runnerID); } public void shutdown() { this.packerThread.terminate(); this.uploadThread.terminate(); } @Override protected void installDefaults(ModeController modeController) { UserAccountController.getController().addUserAccountChangeListener(new IUserAccountChangeListener() { public void activated(UserAccountChangeEvent event) { refreshUploadBuffer(); uploadThread.startUpload(); } public void aboutToDeactivate(UserAccountChangeEvent event) { } }); SwingUtilities.invokeLater(new Runnable() { public void run() { refreshUploadBuffer(); uploadThread.start(); packerThread.start(); } }); } }