/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder 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. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.disk; import static de.dal33t.powerfolder.disk.FolderSettings.FOLDER_SETTINGS_ID; import static de.dal33t.powerfolder.disk.FolderSettings.FOLDER_SETTINGS_PREFIX_V4; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import de.dal33t.powerfolder.ConfigurationEntry; import de.dal33t.powerfolder.Constants; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.Feature; import de.dal33t.powerfolder.Member; import de.dal33t.powerfolder.PFComponent; import de.dal33t.powerfolder.PreferencesEntry; import de.dal33t.powerfolder.clientserver.ServerClient; import de.dal33t.powerfolder.disk.problem.ProblemListener; import de.dal33t.powerfolder.event.FolderAutoCreateEvent; import de.dal33t.powerfolder.event.FolderAutoCreateListener; import de.dal33t.powerfolder.event.FolderRepositoryEvent; import de.dal33t.powerfolder.event.FolderRepositoryListener; import de.dal33t.powerfolder.event.ListenerSupportFactory; import de.dal33t.powerfolder.light.FolderInfo; import de.dal33t.powerfolder.light.MemberInfo; import de.dal33t.powerfolder.message.FileListRequest; import de.dal33t.powerfolder.message.Invitation; import de.dal33t.powerfolder.security.Account; import de.dal33t.powerfolder.security.FolderCreatePermission; import de.dal33t.powerfolder.security.FolderPermission; import de.dal33t.powerfolder.task.CreateFolderOnServerTask; import de.dal33t.powerfolder.task.FolderObtainPermissionTask; import de.dal33t.powerfolder.transfer.FileRequestor; import de.dal33t.powerfolder.util.FileUtils; import de.dal33t.powerfolder.util.IdGenerator; import de.dal33t.powerfolder.util.ProUtil; import de.dal33t.powerfolder.util.Profiling; import de.dal33t.powerfolder.util.ProfilingEntry; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.util.StringUtils; import de.dal33t.powerfolder.util.UserDirectories; import de.dal33t.powerfolder.util.UserDirectory; import de.dal33t.powerfolder.util.Util; import de.dal33t.powerfolder.util.Waiter; import de.dal33t.powerfolder.util.collection.CompositeCollection; import de.dal33t.powerfolder.util.compare.FolderComparator; import de.dal33t.powerfolder.util.os.OSUtil; import de.dal33t.powerfolder.util.os.Win32.WinUtils; import de.dal33t.powerfolder.util.os.mac.MacUtils; import de.schlichtherle.truezip.file.TArchiveDetector; import de.schlichtherle.truezip.file.TFile; import de.schlichtherle.truezip.fs.archive.zip.JarDriver; import de.schlichtherle.truezip.socket.sl.IOPoolLocator; /** * Repository of all known power folders. Local and unjoined. * * @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a> * @version $Revision: 1.75 $ */ public class FolderRepository extends PFComponent implements Runnable { private static final Logger log = Logger.getLogger(FolderRepository.class .getName()); private final Map<FolderInfo, Folder> folders; private final Map<FolderInfo, Folder> metaFolders; private Thread myThread; private final FileRequestor fileRequestor; private Folder currentlyMaintainingFolder; private final Set<String> onLoginFolderEntryIds; // Flag if the repo is started private boolean started; // The trigger to start scanning private final Object scanTrigger = new Object(); private boolean triggered; private final AtomicInteger suspendNewFolderSearch = new AtomicInteger(0); private File foldersBasedir; /** folder repository listeners */ private final FolderRepositoryListener folderRepositoryListenerSupport; /** The disk scanner */ private final FolderScanner folderScanner; /** * The current synchronizater of all folder memberships */ private AllFolderMembershipSynchronizer folderMembershipSynchronizer; private final Object folderMembershipSynchronizerLock = new Object(); /** * Registered to ALL folders to delegate problem event of any folder to * registered listeners. * <p> * TODO: Value listeners deteriorate the UI refresh speed. */ private final ProblemListener valveProblemListenerSupport; private FolderAutoCreateListener folderAutoCreateListener; /** * A list of folder base directories that have been removed in the past. */ private final Set<File> removedFolderDirectories = new CopyOnWriteArraySet<File>(); /** * Constructor * * @param controller */ public FolderRepository(Controller controller) { super(controller); triggered = false; // Rest folders = new ConcurrentHashMap<FolderInfo, Folder>(); metaFolders = new ConcurrentHashMap<FolderInfo, Folder>(); onLoginFolderEntryIds = new HashSet<String>(); fileRequestor = new FileRequestor(controller); started = false; loadRemovedFolderDirectories(); folderScanner = new FolderScanner(getController()); // Create listener support folderRepositoryListenerSupport = ListenerSupportFactory .createListenerSupport(FolderRepositoryListener.class); valveProblemListenerSupport = ListenerSupportFactory .createListenerSupport(ProblemListener.class); folderAutoCreateListener = ListenerSupportFactory .createListenerSupport(FolderAutoCreateListener.class); } private void loadRemovedFolderDirectories() { String list = ConfigurationEntry.REMOVED_FOLDER_FILES .getValue(getController()); String[] parts = list.split("\\$"); for (String s : parts) { File f = new TFile(s); if (f.exists() && f.isDirectory()) { removedFolderDirectories.add(f); } } } public void addProblemListenerToAllFolders(ProblemListener listener) { ListenerSupportFactory.addListener(valveProblemListenerSupport, listener); } public void removeProblemListenerFromAllFolders(ProblemListener listener) { ListenerSupportFactory.removeListener(valveProblemListenerSupport, listener); } public void addFolderAutoCreateListener(FolderAutoCreateListener listener) { ListenerSupportFactory.addListener(folderAutoCreateListener, listener); } public void removeFolderAutoCreateListener(FolderAutoCreateListener listener) { ListenerSupportFactory.removeListener(folderAutoCreateListener, listener); } /** @return The folder scanner that performs the scanning of files on disk */ public FolderScanner getFolderScanner() { return folderScanner; } public void setSuspendFireEvents(boolean suspended) { ListenerSupportFactory.setSuspended(folderRepositoryListenerSupport, suspended); logFine("setSuspendFireEvents: " + suspended); } /** * Load folders from disk. Find all possible folder names, then find config * for each folder name. */ public void init() { // #1697 // Init required. To avoid extracting ZIP/JAR files TFile.setDefaultArchiveDetector(new TArchiveDetector( TArchiveDetector.NULL, "pfzip", new JarDriver( IOPoolLocator.SINGLETON))); TFile.setLenient(false); initFoldersBasedir(); processV4Format(); // Maintain link if (getController().isFirstStart()) { createShortcuts(); } tidyOldLinks(); } public void createShortcuts() { if (PreferencesEntry.CREATE_BASEDIR_DESKTOP_SHORTCUT .getValueBoolean(getController())) { String shortcutName = getController().getFolderRepository() .getFoldersBasedir().getName(); if (Util.isDesktopShortcut(shortcutName)) { Util.removeDesktopShortcut(shortcutName); } Util.createDesktopShortcut(shortcutName, getController() .getFolderRepository().getFoldersBasedir()); } if (PreferencesEntry.CREATE_FAVORITES_SHORTCUT .getValueBoolean(getController())) { try { if (WinUtils.isSupported()) { WinUtils.getInstance().setPFLinks(true, getController()); } else if (MacUtils.isSupported()) { MacUtils.getInstance().setPFPlaces(true, getController()); } } catch (IOException e) { logSevere(e); } } } public void updateShortcuts(String oldShortcutName) { if (PreferencesEntry.CREATE_BASEDIR_DESKTOP_SHORTCUT .getValueBoolean(getController())) { if (Util.isDesktopShortcut(oldShortcutName)) { Util.removeDesktopShortcut(oldShortcutName); String shortcutName = getController().getFolderRepository() .getFoldersBasedir().getName(); Util.createDesktopShortcut(shortcutName, getController() .getFolderRepository().getFoldersBasedir()); } } if (PreferencesEntry.CREATE_FAVORITES_SHORTCUT .getValueBoolean(getController())) { try { if (WinUtils.isSupported() && WinUtils.isPFLinks(oldShortcutName)) { WinUtils.removePFLinks(oldShortcutName); WinUtils.getInstance().setPFLinks(true, getController()); } else if (MacUtils.isSupported()) { MacUtils.getInstance().setPFPlaces(true, getController()); } } catch (IOException e) { logSevere(e); } } } /** * Make sure there are no old links to deleted folders. These should be * maintained when folders are removed. This is just a legacy check. */ private void tidyOldLinks() { File baseDir = getFoldersBasedir(); if (baseDir.exists()) { File[] links = baseDir.listFiles(new FileFilter() { public boolean accept(File pathname) { return pathname.getName() .endsWith(Constants.LINK_EXTENSION); } }); for (File link : links) { // Do we have a folder for this link? boolean haveFolder = false; for (Folder folder : getFolders()) { if ((folder.getName() + Constants.LINK_EXTENSION) .equals(link.getName())) { haveFolder = true; break; } } if (!haveFolder) { // We have a link but no folder; remove link. boolean deleted = link.delete(); log.info("Removed old link " + link.getName() + "? " + deleted); } } } } private void initFoldersBasedir() { String baseDir; String cmdBaseDir = getController().getCommandLine() != null ? getController().getCommandLine().getOptionValue("b") : null; if (StringUtils.isNotBlank(cmdBaseDir)) { baseDir = cmdBaseDir; } else { baseDir = ConfigurationEntry.FOLDER_BASEDIR .getValue(getController()); } // Check if this a windows network drive. boolean winNetworkDrive = baseDir != null && baseDir.contains(":\\") && baseDir.charAt(1) == ':'; boolean ok = false; if (OSUtil.isWindowsSystem() && winNetworkDrive || !winNetworkDrive) { foldersBasedir = new TFile(baseDir).getAbsoluteFile(); if (!foldersBasedir.exists()) { if (foldersBasedir.mkdirs()) { logInfo("Created base path for folders: " + foldersBasedir); } else { logWarning("Unable to create base path for folders: " + foldersBasedir); } } ok = foldersBasedir.exists() && foldersBasedir.canRead() && foldersBasedir.isDirectory(); } if (!OSUtil.isWindowsSystem() && winNetworkDrive) { foldersBasedir = new TFile( ConfigurationEntry.FOLDER_BASEDIR.getDefaultValue()); if (!foldersBasedir.exists()) { if (foldersBasedir.mkdirs()) { logInfo("Created base path for folders: " + foldersBasedir); } else { logWarning("Unable to create base path for folders: " + foldersBasedir); } } ok = foldersBasedir.exists() && foldersBasedir.canRead() && foldersBasedir.isDirectory(); } // Use default as fallback if (!ok && ConfigurationEntry.FOLDER_BASEDIR_FALLBACK_TO_DEFAULT .getValueBoolean(getController())) { foldersBasedir = new TFile( ConfigurationEntry.FOLDER_BASEDIR.getDefaultValue()); if (!foldersBasedir.exists()) { if (foldersBasedir.mkdirs()) { logInfo("Created base path for folders: " + foldersBasedir); } else { logWarning("Unable to create base path for folders: " + foldersBasedir); } } } ok = foldersBasedir.exists() && foldersBasedir.canRead() && foldersBasedir.isDirectory(); if (ok) { logInfo("Using base path for folders: " + foldersBasedir); FileUtils.maintainDesktopIni(getController(), foldersBasedir); } else { logWarning("Unable to access base path for folders: " + foldersBasedir); } } /** * Version 4 format is like f.<md5>.XXXX, where md5 is the MD5 of the folder * id. This format allows folders with the same name to be stored. */ private void processV4Format() { final Properties config = getController().getConfig(); // Find all folder entries. Set<String> entryIds = FolderSettings.loadEntryIds(config); // Load on many processors int loaders = Math.min(Runtime.getRuntime().availableProcessors() - 2, 8); if (loaders <= 0) { loaders = 1; } final Semaphore loadPermit = new Semaphore(loaders); final AtomicInteger nCreated = new AtomicInteger(); // Scan config for all found folder MD5s. for (final String folderEntryId : entryIds) { try { loadPermit.acquire(); } catch (InterruptedException e) { logFiner(e); return; } Runnable folderCreator = new Runnable() { public void run() { try { String folderId = config .getProperty(FOLDER_SETTINGS_PREFIX_V4 + folderEntryId + FOLDER_SETTINGS_ID); if (StringUtils.isBlank(folderId)) { logWarning("Folder id blank. Removed illegal folder config entry: " + folderEntryId); removeConfigEntries(folderEntryId); return; } String folderName = FolderSettings.loadFolderName( getController().getConfig(), folderEntryId); if (StringUtils.isBlank(folderName)) { logWarning("Foldername not found." + "Removed illegal folder config entry: " + folderName + '/' + folderEntryId); removeConfigEntries(folderEntryId); return; } // #2203 Load later if folder id should be taken from // account. if (folderId .contains(FolderSettings.FOLDER_ID_FROM_ACCOUNT)) { logFine("Folder load scheduled after first login: " + folderName + '/' + folderEntryId); onLoginFolderEntryIds.add(folderEntryId); return; } boolean spawned = false; if (folderId .contains(FolderSettings.FOLDER_ID_GENERATE)) { String generatedId = '[' + IdGenerator.makeId() + ']'; folderId = folderId.replace( FolderSettings.FOLDER_ID_GENERATE, generatedId); logInfo("Spawned new folder id for config entry " + folderEntryId + ": " + folderId); spawned = true; } FolderInfo foInfo = new FolderInfo(folderName, folderId) .intern(); FolderSettings folderSettings = FolderSettings.load( getController(), folderEntryId); if (folderSettings == null) { logWarning("Unable to load folder settings." + "Removed folder config entry: " + folderName + '/' + folderEntryId); removeConfigEntries(folderEntryId); return; } // Do not add0 if already added if (!hasJoinedFolder(foInfo) && folderId != null && folderSettings != null) { createFolder0(foInfo, folderSettings, false); } if (spawned) { removeConfigEntries(folderEntryId); folderSettings.set(foInfo, config); getController().saveConfig(); } } catch (Exception e) { logSevere("Problem loading/creating folder #" + folderEntryId + ". " + e, e); } finally { loadPermit.release(); synchronized (nCreated) { nCreated.incrementAndGet(); nCreated.notify(); } } } }; getController().getIOProvider().startIO(folderCreator); } // Wait for creators to complete while (nCreated.get() < entryIds.size()) { synchronized (nCreated) { try { nCreated.wait(10); } catch (InterruptedException e) { logFiner(e); return; } } } logInfo("Loaded " + getFoldersCount() + " folders"); } /** * Starts the folder repo maintenance thread */ public void start() { if (!ConfigurationEntry.FOLDER_REPOSITORY_ENABLED .getValueBoolean(getController())) { logWarning("Not starting FolderRepository. disabled by config"); return; } folderScanner.start(); // Now start thread myThread = new Thread(this, getClass().getName()); // set to min priority myThread.setPriority(Thread.MIN_PRIORITY); myThread.start(); // Start file requestor fileRequestor.start(); // Defer 2 minutes, so it is not 'in-your-face' at start up. // Also run this every minute. getController().scheduleAndRepeat(new CheckSyncTask(), 1000L * Constants.FOLDER_UNSYNCED_CHECK_DELAY, Constants.MILLIS_PER_MINUTE); started = true; } /** * Shuts down folder repo */ public void shutdown() { synchronized (folderMembershipSynchronizerLock) { if (folderMembershipSynchronizer != null) { folderMembershipSynchronizer.canceled = true; } } folderScanner.shutdown(); if (myThread != null) { myThread.interrupt(); } synchronized (scanTrigger) { scanTrigger.notifyAll(); } // Stop processor // netListProcessor.shutdown(); // Stop file requestor fileRequestor.shutdown(); // shutdown all folders for (Folder metaFolder : metaFolders.values()) { metaFolder.shutdown(); } for (Folder folder : folders.values()) { folder.shutdown(); } // make sure that on restart of folder the folders are freshly read folders.clear(); metaFolders.clear(); logFine("Stopped"); } /** * @return the default basedir for all folders. basedir is just suggested */ public String getFoldersBasedirString() { return getFoldersBasedir() != null ? getFoldersBasedir() .getAbsolutePath() : null; } /** * @return the default basedir for all folders. basedir is just suggested */ public File getFoldersBasedir() { if (foldersBasedir == null) { initFoldersBasedir(); } return foldersBasedir; } /** * Sets the new base path * * @param path */ public void setFoldersBasedir(String path) { if (path == null) { ConfigurationEntry.FOLDER_BASEDIR.removeValue(getController()); return; } ConfigurationEntry.FOLDER_BASEDIR.setValue(getController(), path); initFoldersBasedir(); } /** * @return the file requestor */ public FileRequestor getFileRequestor() { return fileRequestor; } /** * @param info * @return if folder is in repo */ public boolean hasJoinedFolder(FolderInfo info) { if (!info.isMetaFolder()) { return folders.containsKey(info); } for (Folder folder : metaFolders.values()) { if (folder.getInfo().equals(info)) { return true; } } return false; } /** * @param folderId * @return the folder by folder id, or null if folder is not found */ public Folder getFolder(String folderId) { return getFolder(new FolderInfo("", folderId)); } /** * @param info * @return the folder by info, or null if folder is not found */ public Folder getFolder(FolderInfo info) { if (!info.isMetaFolder()) { return folders.get(info); } // #1548: Speed this up. for (Folder metaFolder : metaFolders.values()) { if (metaFolder.getInfo().equals(info)) { return metaFolder; } } return null; } /** * All real-folders WITHOUT Meta-folders (#1548). Returns the indirect * reference to the internal {@link ConcurrentMap}. Contents may changed * after get. * * @return the folders as unmodifiable collection */ public Collection<Folder> getFolders() { return getFolders(false); } /** * All real-folders WITH or WITHOUT Meta-folders (#1548). Returns the * indirect reference to the internal {@link ConcurrentMap}. Contents may * changed after get. * * @param includeMetaFolders * @return the folders as unmodifiable collection */ public Collection<Folder> getFolders(boolean includeMetaFolders) { if (!includeMetaFolders) { return Collections.unmodifiableCollection(folders.values()); } CompositeCollection<Folder> composite = new CompositeCollection<Folder>(); composite.addComposited(folders.values(), metaFolders.values()); return Collections.unmodifiableCollection(composite); } /** * @return the number of folders. Does NOT include the meta-folders (#1548). */ public int getFoldersCount() { return getFoldersCount(false); } /** * @param includeMetaFolders * @return the number of folders */ public int getFoldersCount(boolean includeMetaFolders) { return folders.size() + (includeMetaFolders ? metaFolders.size() : 0); } /** * @return an unmodifiable, but thread safe collection of all joined real * folders, does NOT include meta-folders (#1548) */ public Collection<FolderInfo> getJoinedFolderInfos() { return Collections.unmodifiableCollection(folders.keySet()); } /** * Finds an folder on the give target directory. * * @param targetDir * @return the folder with the targetDir as local base or null if not found */ public Folder findExistingFolder(File targetDir) { for (Folder folder : getController().getFolderRepository().getFolders()) { try { if (folder.getLocalBase().equals(targetDir) || folder.getCommitOrLocalDir().getCanonicalPath() .equals(targetDir.getCanonicalPath())) { return folder; } } catch (IOException e) { logWarning(e); } } return null; } /** * Finds an folder with the give folder name. Search is non-case sensitive! * * @param folderName * @return the folder with the given name or null if not found */ public Folder findExistingFolder(String folderName) { for (Folder folder : getController().getFolderRepository().getFolders()) { if (folder.getName().equalsIgnoreCase(folderName)) { return folder; } } return null; } /** * Creates a folder from a folder info object and sets the sync profile. * <p> * Also stores a invitation file for the folder in the local directory if * wanted. * * @param folderInfo * the folder info object * @param folderSettings * the settings for the folder * @return the freshly created folder */ public Folder createFolder(FolderInfo folderInfo, FolderSettings folderSettings) { if (!folderSettings.getLocalBaseDir().getName().endsWith(".pfzip")) { folderSettings.getLocalBaseDir().mkdirs(); } Folder folder = createFolder0(folderInfo, folderSettings, true); // Obtain permission. Don't do this on startup (createFolder0) if (getController().getOSClient().isLoggedIn() && !getController().getOSClient().getAccount() .hasPermission(FolderPermission.read(folderInfo))) { getController().getTaskManager().scheduleTask( new FolderObtainPermissionTask(getController().getOSClient() .getAccountInfo(), folder.getInfo())); } return folder; } /** * Used when creating a preview folder. FolderSettings should be as required * for the preview folder. Note that settings are not stored and the caller * is responsible for setting the preview config. * * @param folderInfo * @param folderSettings * @return the preview folder. */ public Folder createPreviewFolder(FolderInfo folderInfo, FolderSettings folderSettings) { return createFolder0(folderInfo, folderSettings, false); } /** * Creates a folder from a folder info object and sets the sync profile. * <p> * Also stores an invitation file for the folder in the local directory if * wanted. * * @param folderInfo * the folder info object * @param folderSettings * the settings for the folder * @param saveConfig * true if the configuration file should be saved after creation. * @return the freshly created folder */ private Folder createFolder0(FolderInfo folderInfo, FolderSettings folderSettings, boolean saveConfig) { Reject.ifNull(folderInfo, "FolderInfo is null"); Reject.ifNull(folderSettings, "FolderSettings is null"); if (hasJoinedFolder(folderInfo)) { return folders.get(folderInfo); } // If non-preview folder and already have this folder as preview, // silently remove the preview. if (!folderSettings.isPreviewOnly()) { for (Folder folder : folders.values()) { if (folder.isPreviewOnly() && folder.getInfo().equals(folderInfo)) { logInfo("Removed preview folder " + folder.getName()); removeFolder(folder, true); break; } if (folder.getCommitOrLocalDir().equals( folderSettings.getLocalBaseDir())) { logSevere("Tried to create duplicate folder " + folder.getName() + ". at " + folder.getCommitOrLocalDir() + ". Existing folder ID: " + folder.getId() + ". Requested folder ID: " + folderInfo.getId()); throw new IllegalStateException( "Tried to create duplicate folder " + folder.getName() + ". at " + folder.getCommitOrLocalDir() + ". Existing folder ID: " + folder.getId() + ". Requested folder ID: " + folderInfo.getId()); } } } // PFC-2226: Option to restrict new folder creation to the default // storage path. // Note, this is a last check. User should never get here because of // other checks. if (ConfigurationEntry.FOLDER_CREATE_IN_BASEDIR_ONLY .getValueBoolean(getController())) { boolean inBaseDir = folderSettings.getLocalBaseDir() .getParentFile().equals(getFoldersBasedir()); if (!inBaseDir) { logSevere("Not allowed to create " + folderInfo.getName() + " at " + folderSettings.getLocalBaseDirString() + ". Must be in base directory: " + getFoldersBasedir()); throw new IllegalStateException("Not allowed to create " + folderInfo.getName() + " at " + folderSettings.getLocalBaseDirString() + ". Must be in base directory: " + getFoldersBasedir()); } } if (Feature.FOLDER_ATOMIC_COMMIT.isEnabled() && folderSettings.getCommitDir() == null) { File newBaseDir = new TFile(folderSettings.getLocalBaseDir(), Constants.ATOMIC_COMMIT_TEMP_TARGET_DIR); newBaseDir.mkdirs(); FileUtils.setAttributesOnWindows(newBaseDir, true, true); File commitDir = folderSettings.getLocalBaseDir(); SyncProfile syncProfile = SyncProfile.NO_SYNC; folderSettings = new FolderSettings(newBaseDir, syncProfile, false, folderSettings.isPreviewOnly(), folderSettings.getDownloadScript(), folderSettings.getVersions(), folderSettings.isSyncPatterns(), commitDir, folderSettings.getSyncWarnSeconds()); logWarning("Auto-commit setup. temp dir: " + newBaseDir + ". commit dir:" + commitDir); } Folder folder; if (folderSettings.isPreviewOnly()) { // Need to use preview folder settings. FolderSettings previewFolderSettings = FolderPreviewHelper .createPreviewFolderSettings(folderInfo.name); folder = new Folder(getController(), folderInfo, previewFolderSettings); } else { folder = new Folder(getController(), folderInfo, folderSettings); } folder.addProblemListener(valveProblemListenerSupport); // Now create metaFolder and map to the same FolderInfo key. FolderInfo metaFolderInfo = folderInfo.getMetaFolderInfo(); File systemSubdir = new TFile(folder.getLocalBase(), Constants.POWERFOLDER_SYSTEM_SUBDIR); FolderSettings metaFolderSettings = new FolderSettings(new TFile( systemSubdir, Constants.METAFOLDER_SUBDIR), SyncProfile.META_FOLDER_SYNC, false, 0); boolean deviceDisconnected = folder.checkIfDeviceDisconnected(); if (!deviceDisconnected) { metaFolderSettings.getLocalBaseDir().mkdirs(); } Folder metaFolder = new Folder(getController(), metaFolderInfo, metaFolderSettings); if (!deviceDisconnected) { metaFolder.getSystemSubDir().mkdirs(); } // Set datamodel metaFolders.put(folderInfo, metaFolder); folders.put(folder.getInfo(), folder); saveFolderConfig(folderInfo, folderSettings, saveConfig); if (!metaFolder.hasOwnDatabase()) { // Scan once. To get it working. metaFolder.setSyncProfile(SyncProfile.MANUAL_SYNCHRONIZATION); metaFolder.recommendScanOnNextMaintenance(true); } logFine("Created metaFolder " + metaFolderInfo.name + ", local copy at '" + metaFolderSettings.getLocalBaseDir() + '\''); // Synchronize folder memberships triggerSynchronizeAllFolderMemberships(); // Calc stats folder.getStatistic().scheduleCalculate(); // Trigger scan getController().getFolderRepository().triggerMaintenance(); // Trigger file requestor fileRequestor.triggerFileRequesting(folder.getInfo()); // Fire event fireFolderCreated(folder); if (isFine()) { String message = "Setup " + (folder.isEncrypted() ? "encrypted " : "") + "folder " + folderInfo.name + " at " + folder.getLocalBase(); logFine(message); } removeFromRemovedFolderDirectories(folder); return folder; } /** * Saves settings and info details to the config. * * @param folderInfo * @param folderSettings * @param saveConfig */ public void saveFolderConfig(FolderInfo folderInfo, FolderSettings folderSettings, boolean saveConfig) { // store folder in config Properties config = getController().getConfig(); folderSettings.set(folderInfo, config); if (saveConfig) { getController().saveConfig(); } } /** * Removes a folder from active folders, will be added as non-local folder * * @param folder * @param deleteSystemSubDir */ public void removeFolder(Folder folder, boolean deleteSystemSubDir) { removeFolder(folder, deleteSystemSubDir, true); } /** * Removes a folder from active folders, will be added as non-local folder * * @param folder * @param deleteSystemSubDir * @param saveConfig */ public void removeFolder(Folder folder, boolean deleteSystemSubDir, boolean saveConfig) { Reject.ifNull(folder, "Folder is null"); try { suspendNewFolderSearch.incrementAndGet(); // Remove link if it exists. removeLink(folder); // Remember that we have removed this folder. addToRemovedFolderDirectories(folder); // Remove the desktop shortcut folder.removeDesktopShortcut(); // Detach any problem listeners. folder.clearAllProblemListeners(); // Remove desktop ini if it exists FileUtils.deleteDesktopIni(folder.getLocalBase()); // remove folder from config removeConfigEntries(folder.getConfigEntryId()); // Save config if (saveConfig) { getController().saveConfig(); } // Shutdown meta folder as well Folder metaFolder = getMetaFolderForParent(folder.getInfo()); if (metaFolder != null) { metaFolder.shutdown(); metaFolders.remove(metaFolder.getInfo()); metaFolders.remove(folder.getInfo()); } // Remove internal folders.remove(folder.getInfo()); folder.removeProblemListener(valveProblemListenerSupport); // Break transfers getController().getTransferManager().breakTransfers( folder.getInfo()); // Shutdown folder folder.shutdown(); // synchronize memberships triggerSynchronizeAllFolderMemberships(); // Abort scanning boolean folderCurrentlyScannng = folder.equals(folderScanner .getCurrentScanningFolder()); if (folderCurrentlyScannng) { folderScanner.abortScan(); } // Delete the .PowerFolder dir and contents if (deleteSystemSubDir) { // Sleep a couple of seconds for things to settle, // before removing dirs, to avoid conflicts. try { Thread.sleep(50); } catch (InterruptedException e) { } try { FileUtils.recursiveDelete(folder.getSystemSubDir()); } catch (IOException e) { logSevere("Failed to delete: " + folder.getSystemSubDir()); } // Try to delete the invitation. File invite = new TFile(folder.getLocalBase(), folder.getName() + ".invitation"); if (invite.exists()) { try { invite.delete(); } catch (Exception e) { logSevere( "Failed to delete invitation: " + invite.getAbsolutePath(), e); } } // Remove the folder if totally empty. File[] files = folder.getLocalBase().listFiles(); if (files != null && files.length == 0) { try { FileUtils.recursiveDelete(folder.getLocalBase()); } catch (Exception e) { logSevere("Failed to delete local base: " + folder.getLocalBase().getAbsolutePath(), e); } } } } finally { suspendNewFolderSearch.decrementAndGet(); } // Fire event fireFolderRemoved(folder); // Trigger sync on other folders. fileRequestor.triggerFileRequesting(); if (isFine()) { logFine(folder + " removed"); } } /** * Remove the link to the folder if it exists. * * @param folder */ private void removeLink(Folder folder) { FolderRepository repository = getController().getFolderRepository(); File baseDir = repository.getFoldersBasedir(); if (baseDir.exists()) { File shortcutFile = new File(baseDir, folder.getName() + Constants.LINK_EXTENSION); if (shortcutFile.exists()) { boolean deleted = shortcutFile.delete(); log.info("Removed link " + shortcutFile.getName() + "? " + deleted); } } } private void addToRemovedFolderDirectories(Folder folder) { if (removedFolderDirectories.add(folder.getLocalBase())) { StringBuilder sb = new StringBuilder(); Iterator<File> iterator = removedFolderDirectories.iterator(); while (iterator.hasNext()) { String s = iterator.next().getAbsolutePath(); sb.append(s); if (iterator.hasNext()) { sb.append('$'); } } ConfigurationEntry.REMOVED_FOLDER_FILES.setValue(getController(), sb.toString()); } } private void removeFromRemovedFolderDirectories(Folder folder) { if (removedFolderDirectories.remove(folder.getLocalBase())) { StringBuilder sb = new StringBuilder(); Iterator<File> iterator = removedFolderDirectories.iterator(); while (iterator.hasNext()) { String s = iterator.next().getAbsolutePath(); sb.append(s); if (iterator.hasNext()) { sb.append('$'); } } ConfigurationEntry.REMOVED_FOLDER_FILES.setValue(getController(), sb.toString()); } } /** * Removes a member from all Folders. * * @param member */ public void removeFromAllFolders(Member member) { if (isFiner()) { logFiner("Removing node from all folders: " + member); } for (Folder folder : getFolders(true)) { folder.remove(member); } if (isFiner()) { logFiner("Node removed from all folders: " + member); } } /** * Triggers the synchronization of all known members with our folders. The * work is done in background thread. Former synchronization processed the * canceled. */ public void triggerSynchronizeAllFolderMemberships() { if (!started) { logFiner("Not synchronizing Foldermemberships, repo not started, yet"); } synchronized (folderMembershipSynchronizerLock) { if (folderMembershipSynchronizer != null) { // Cancel the syncer folderMembershipSynchronizer.canceled = true; } folderMembershipSynchronizer = new AllFolderMembershipSynchronizer(); getController().schedule(folderMembershipSynchronizer, 0); } } /** * Broadcasts a remote scan commando on all folders. */ public void broadcastScanCommandOnAllFolders() { if (log.isLoggable(Level.FINE)) { logFine("Sending remote scan commando"); } for (Folder folder : getFolders(true)) { folder.broadcastScanCommand(); } } /** * @return the folder that currently gets maintainted or null if not * maintaining any folder. */ public Folder getCurrentlyMaintainingFolder() { return currentlyMaintainingFolder; } /** * Triggers the maintenance on all folders. may or may not scan the folders * - depending on settings. */ public void triggerMaintenance() { if (isFiner()) { logFiner("Scan triggerd"); } triggered = true; synchronized (scanTrigger) { scanTrigger.notifyAll(); } } /** * Mainenance thread for the folders */ public void run() { // 1000 ms wait long waitTime = Controller.getWaitTime() / 5; // Wait to build up ui Waiter w = new Waiter(30L * 1000); while (!w.isTimeout()) { if (getController().isUIEnabled() && getController().isUIOpen()) { break; } if (getController().isStarted()) { break; } try { w.waitABit(); } catch (Exception e) { return; } } // try { // // initial wait before first scan // synchronized (scanTrigger) { // scanTrigger.wait(Controller.getWaitTime() * 4); // } // } catch (InterruptedException e) { // logFiner(e); // return; // } List<Folder> scanningFolders = new ArrayList<Folder>(); Controller controller = getController(); while (!myThread.isInterrupted() && myThread.isAlive()) { // Only scan if not in paused mode if (!controller.isPaused()) { scanningFolders.clear(); for (Folder folder : folders.values()) { if (folder.isMaintenanceRequired()) { scanningFolders.add(folder); } } for (Folder metaFolder : metaFolders.values()) { if (metaFolder.isMaintenanceRequired()) { scanningFolders.add(metaFolder); } } Collections.sort(scanningFolders, FolderComparator.INSTANCE); if (isFiner()) { logFiner("Maintaining " + scanningFolders.size() + " folders..."); } for (Folder folder : scanningFolders) { currentlyMaintainingFolder = folder; // Fire event fireMaintanceStarted(currentlyMaintainingFolder); currentlyMaintainingFolder.maintain(); Folder maintainedFolder = currentlyMaintainingFolder; currentlyMaintainingFolder = null; // Fire event fireMaintenanceFinished(maintainedFolder); if (controller.isPaused() || myThread.isInterrupted()) { break; } // Wait a bit to give other waiting sync processes time... try { Thread.sleep(50); } catch (InterruptedException e) { break; } } if (isFiner()) { logFiner("Maintained " + scanningFolders.size() + " folder(s)"); } } if (!triggered) { try { // use waiter, will quit faster synchronized (scanTrigger) { scanTrigger.wait(waitTime); } } catch (InterruptedException e) { logFiner(e); break; } } triggered = false; } } /* * General */ @Override public String toString() { return "Folders of " + getController().getMySelf().getNick(); } /** * ATTENTION: This is a stack based system. When suspending the search do it * only ONCE and make sure you release the lock in a finally block Can be * set by the UI when we are creating folders so that lookForNewFolders does * not jump in while the user is setting up a new folder in a Wizard or * something. Don't forget to set this back to false when finished. * * @param activity */ public void setSuspendNewFolderSearch(boolean activity) { if (activity) { suspendNewFolderSearch.incrementAndGet(); } else { suspendNewFolderSearch.decrementAndGet(); } logFine("setSuspendNewFolderSearch to " + activity + " now: " + suspendNewFolderSearch.get()); } /** * Scan the PowerFolder base directory for new directories that might be new * folders. */ public void lookForNewFolders() { if (suspendNewFolderSearch.get() > 0) { return; } if (!getController().getMySelf().isServer()) { if (!getController().getOSClient().isLoggedIn()) { if (isFine()) { logFine("Skipping searching for new folders..."); } return; } if (ConfigurationEntry.SECURITY_PERMISSIONS_STRICT .getValueBoolean(getController()) && !getController().getOSClient().getAccount() .hasPermission(FolderCreatePermission.INSTANCE)) { if (isFine()) { logFine("Skipping searching for new folders (no permission)..."); } return; } } if (getController().isPaused()) { logFine("Skipping searching for new folders (paused)..."); return; } if (isFine()) { logFine("Searching for new folders..."); } // TODO BOTTLENECK: Takes much CPU -> Implement via jnotify String baseDirName = getFoldersBasedirString(); File baseDir = new TFile(baseDirName); if (baseDir.exists() && baseDir.canRead()) { // Get all directories File[] directories = baseDir.listFiles(new FileFilter() { public boolean accept(File file) { String filename = file.getName(); if (filename.equals(Constants.POWERFOLDER_SYSTEM_SUBDIR)) { return false; } if (filename.equals("BACKUP_REMOVE")) { return false; } if (!file.isDirectory()) { return false; } // Don't autocreate if it has been removed previously. if (removedFolderDirectories.contains(file)) { return false; } return true; } }); for (File dir : directories) { boolean known = false; for (Folder folder : getFolders()) { if (folder.getName().equals(dir.getName())) { known = true; break; } File localBase = folder.getLocalBase(); if (localBase.equals(dir) || localBase.getAbsolutePath().startsWith( dir.getAbsolutePath())) { known = true; break; } } if (!known && FileUtils.hasContents(dir)) { handleNewFolder(dir); } } } } // Found a new directory in the folder base. Create a new folder. // Only doing this if logged in. private void handleNewFolder(File file) { FolderInfo fi = null; Controller controller = getController(); ServerClient client = controller.getOSClient(); if (client.isConnected() && client.isLoggedIn()) { for (FolderInfo folderInfo : client.getAccountFolders()) { if (folderInfo.getName().equals(file.getName())) { fi = folderInfo; break; } } } if (fi == null) { fi = new FolderInfo(file.getName(), '[' + IdGenerator.makeId() + ']'); } FolderSettings fs = new FolderSettings(file, SyncProfile.AUTOMATIC_SYNCHRONIZATION, false, ConfigurationEntry.DEFAULT_ARCHIVE_VERSIONS.getValueInt(controller)); Folder folder = createFolder(fi, fs); folder.addDefaultExcludes(); if (client.isBackupByDefault()) { if (client.isConnected() && client.isLoggedIn()) { boolean joined = client.joinedByCloud(folder); if (!joined) { new CreateFolderOnServerTask(client.getAccountInfo(), fi, null).scheduleTask(getController()); } } } logInfo("Auto-created new folder: " + folder + " @ " + folder.getLocalBase()); folderAutoCreateListener .folderAutoCreated(new FolderAutoCreateEvent(fi)); } /** * In sync = all folders are 100% synced and all syncing actions have * stopped. * * @return true if all folders are 100% in sync */ public boolean isInSync() { for (Folder folder : getFolders()) { if (!folder.isInSync()) { if (isWarning()) { logWarning(folder + " not in sync yet"); } return false; } } return true; } /** * Gets a metaFolder for a FolderInfo. NOTE: the folderInfo is the parent * Folder's FolderInfo, NOT the FolderInfo of the metaFolder. BUT the * metaFolders Map key holds the parent FolderInfo * * @param parentFolderInfo * parent Folder's FolderInfo * @return the meta folder. */ public Folder getMetaFolderForParent(FolderInfo parentFolderInfo) { return metaFolders.get(parentFolderInfo); } /** * @param metaFolderInfo * @return the parent folder for a metaFolder's info. */ public Folder getParentFolder(FolderInfo metaFolderInfo) { if (!metaFolderInfo.isMetaFolder()) { return null; } return getFolder(metaFolderInfo.getParentFolderInfo()); } /** * Automatically accept an invitation. If not able to, silently return * false. * * @param invitation * @return true if the invitation was accepted. */ public boolean autoAcceptInvitation(Invitation invitation) { // Defensive strategy: Place in PowerFolders\... File suggestedLocalBase; if (ConfigurationEntry.FOLDER_CREATE_USE_EXISTING .getValueBoolean(getController())) { // Moderate strategy. Use existing folders. suggestedLocalBase = new TFile(getController() .getFolderRepository().getFoldersBasedir(), invitation.folder.name); if (suggestedLocalBase.exists()) { logWarning("Using existing directory " + suggestedLocalBase + " for " + invitation.folder); } } else { // Defensive strategy. Find free new empty directory. suggestedLocalBase = FileUtils.createEmptyDirectory(getController() .getFolderRepository().getFoldersBasedir(), invitation.folder.name); } // Is this invitation from a friend? boolean invitorIsFriend = false; MemberInfo memberInfo = invitation.getInvitor(); if (memberInfo != null) { Member node = getController().getNodeManager().getNode(memberInfo); if (node != null) { invitorIsFriend = node.isFriend(); } } if (!invitorIsFriend) { logInfo("Not auto accepting " + invitation + " because " + memberInfo + " is not a friend."); return false; } logInfo("AutoAccepting " + invitation + " from " + memberInfo + '.'); FolderSettings folderSettings = new FolderSettings(suggestedLocalBase, invitation.getSuggestedSyncProfile(), false, ConfigurationEntry.DEFAULT_ARCHIVE_VERSIONS .getValueInt(getController())); createFolder(invitation.folder, folderSettings); return true; } // Callbacks from ServerClient on login *********************************** private ReentrantLock accountSyncLock = new ReentrantLock(); public void updateFolders(Account a) { // TODO: Called too often Reject.ifNull(a, "Account"); if (getController().getMySelf().isServer()) { return; } if (!a.isValid()) { return; } accountSyncLock.lock(); try { logInfo("Syncing folder setup with account permissions(" + a.getFolders().size() + "): " + a.getUsername()); Collection<FolderInfo> created = createLocalFolders(a); if (ConfigurationEntry.SECURITY_PERMISSIONS_STRICT .getValueBoolean(getController())) { if (ProUtil.isServerConfig(getController())) { logSevere("Found server config running with client installation. " + "Won't delete local folders."); return; } removeLocalFolders(a, created); } } finally { accountSyncLock.unlock(); } } private void removeLocalFolders(Account a, Collection<FolderInfo> skip) { if (!a.isValid()) { return; } for (Folder folder : getFolders()) { if (skip.contains(folder.getInfo())) { continue; } if (!a.hasReadPermissions(folder.getInfo())) { logWarning("Removing local " + folder + ' ' + a + " does not have read permission. Wiping out data."); removeFolder(folder, true); final File localBase = folder.getLocalBase(); getController().getIOProvider().startIO(new Runnable() { public void run() { try { Thread.sleep(2000L); FileUtils.recursiveDelete(localBase); } catch (Exception e) { logWarning("Unable to delete directory: " + localBase); } } }); } } } private synchronized Collection<FolderInfo> createLocalFolders(Account a) { if (!a.isValid()) { return Collections.emptyList(); } Collection<FolderInfo> folderInfos = new ArrayList<FolderInfo>(); for (Iterator<String> it = onLoginFolderEntryIds.iterator(); it .hasNext();) { String folderEntryId = it.next(); FolderSettings settings = FolderSettings.load(getController(), folderEntryId); String folderName = FolderSettings.loadFolderName(getController() .getConfig(), folderEntryId); if (settings == null) { String folderDirStr = getController().getConfig().getProperty( FOLDER_SETTINGS_PREFIX_V4 + folderEntryId + FolderSettings.FOLDER_SETTINGS_DIR); logWarning("Not setting up folder " + folderName + " / " + folderEntryId + " local base dir not found: " + folderDirStr); continue; } FolderInfo foInfo = null; for (FolderInfo candidate : a.getFolders()) { if (candidate.getName().equals(folderName)) { logInfo("Folder found on account " + a.getUsername() + ". Loading it: " + candidate); foInfo = candidate; break; } } // Actually create the directory try { settings.getLocalBaseDir().mkdirs(); if (foInfo != null) { // Load existing. createFolder0(foInfo, settings, true); } else { // Spawn/Create a new one. foInfo = new FolderInfo(folderName, '[' + IdGenerator.makeId() + ']'); Folder folder = createFolder(foInfo, settings); folder.addDefaultExcludes(); logWarning("Folder NOT found on account " + a.getUsername() + ". Created new: " + foInfo); } // Make sure it is backed up by the server. CreateFolderOnServerTask task = new CreateFolderOnServerTask( a.createInfo(), foInfo, null); task.setArchiveVersions(settings.getVersions()); getController().getTaskManager().scheduleTask(task); // Remove from pending entries. it.remove(); folderInfos.add(foInfo); } catch (Exception e) { logWarning("Unable to create folder " + folderName + ". " + e); } } if (ConfigurationEntry.AUTO_SETUP_ACCOUNT_FOLDERS .getValueBoolean(getController())) { for (FolderInfo folderInfo : a.getFolders()) { if (hasJoinedFolder(folderInfo)) { continue; } SyncProfile profile = SyncProfile.getDefault(getController()); File suggestedLocalBase = new TFile(getController() .getFolderRepository().getFoldersBasedir(), folderInfo.name); if (removedFolderDirectories.contains(suggestedLocalBase)) { continue; } UserDirectory userDir = UserDirectories.getUserDirectories() .get(folderInfo.name); if (userDir != null) { if (removedFolderDirectories.contains(userDir .getDirectory())) { continue; } suggestedLocalBase = userDir.getDirectory(); } else if (ConfigurationEntry.FOLDER_CREATE_USE_EXISTING .getValueBoolean(getController())) { // Moderate strategy. Use existing folders. suggestedLocalBase = new TFile(getController() .getFolderRepository().getFoldersBasedir(), folderInfo.name); if (suggestedLocalBase.exists()) { logWarning("Using existing directory " + suggestedLocalBase + " for " + folderInfo); } } else { // Take folder name as subdir name suggestedLocalBase = new TFile(getController() .getFolderRepository().getFoldersBasedir(), folderInfo.name); } logInfo("Auto setting up folder " + folderInfo + " for account " + a.getUsername() + " @ " + suggestedLocalBase); // Correct local path if in UserDirectories. FolderSettings settings = new FolderSettings( suggestedLocalBase, profile, false, ConfigurationEntry.DEFAULT_ARCHIVE_VERSIONS .getValueInt(getController())); // Actually create the directory settings.getLocalBaseDir().mkdirs(); try { Folder folder = createFolder0(folderInfo, settings, true); folder.addDefaultExcludes(); folderInfos.add(folderInfo); } catch (Exception e) { logWarning("Unable to create folder " + folderInfo.getName() + ". " + e); } } } return folderInfos; } // Event support ********************************************************** private void fireFolderCreated(Folder folder) { folderRepositoryListenerSupport .folderCreated(new FolderRepositoryEvent(this, folder)); } private void fireFolderRemoved(Folder folder) { folderRepositoryListenerSupport .folderRemoved(new FolderRepositoryEvent(this, folder)); } private void fireMaintanceStarted(Folder folder) { folderRepositoryListenerSupport .maintenanceStarted(new FolderRepositoryEvent(this, folder)); } private void fireMaintenanceFinished(Folder folder) { folderRepositoryListenerSupport .maintenanceFinished(new FolderRepositoryEvent(this, folder)); } public void addFolderRepositoryListener(FolderRepositoryListener listener) { ListenerSupportFactory.addListener(folderRepositoryListenerSupport, listener); } public void removeFolderRepositoryListener(FolderRepositoryListener listener) { ListenerSupportFactory.removeListener(folderRepositoryListenerSupport, listener); } private void removeConfigEntries(String folderEntryId) { Properties config = getController().getConfig(); FolderSettings.removeEntries(config, folderEntryId); } /** * Delete any file archives over a specified age. */ public void cleanupOldArchiveFiles() { int period = ConfigurationEntry.DEFAULT_ARCHIVE_CLEANUP_DAYS .getValueInt(getController()); if (period == Integer.MAX_VALUE || period <= 0) { // cleanup := never return; } Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, -period); Date cleanupDate = cal.getTime(); for (Folder folder : getFolders()) { folder.cleanupOldArchiveFiles(cleanupDate); } } /** * Do we already have a folder that has this file as its base? * * @param file */ public boolean doesFolderAlreadyExist(File file) { if (!file.isDirectory()) { return false; } for (Folder folder : folders.values()) { if (file.equals(folder.getBaseDirectoryInfo().getDiskFile(this))) { return true; } } return false; } public boolean areAllFoldersInSync() { for (Folder folder : folders.values()) { if (Double.compare(folder.getStatistic() .getHarmonizedSyncPercentage(), 100.0d) < 0) { return false; } } return true; } private class CheckSyncTask implements Runnable { public void run() { for (Folder folder : getController().getFolderRepository() .getFolders()) { if (folder.isPreviewOnly()) { continue; } folder.checkSync(); if (folder.getStatistic().getHarmonizedSyncPercentage() == 100.0d) { continue; } if (folder.getConnectedMembersCount() == 0) { continue; } if (folder.getKnownItemCount() == 0) { continue; } if (!folder.hasReadPermission(getController().getMySelf())) { continue; } // Rationale: We might have not received file list from a server // PFC-2368 for (Member member : folder.getConnectedMembers()) { if (!member.hasCompleteFileListFor(folder.getInfo())) { // Might still be transferring those filelists. continue; } int nMemberItems = folder.getDAO().count(member.getId(), true, false); if (nMemberItems > 0) { continue; } // OK: Handle it. There is a connected member on an unsyced // folder, which has send ZERO files. logInfo("Re-requesting file list for " + folder.getName() + " from " + member.getNick()); member.sendMessageAsynchron(new FileListRequest(folder .getInfo())); } } } } private class AllFolderMembershipSynchronizer implements Runnable { private volatile boolean canceled; public void run() { ProfilingEntry pe = Profiling .start("synchronizeAllFolderMemberships"); try { if (canceled) { logFine("Not synchronizing Foldermemberships, " + "operation already canceled yet"); return; } if (isFiner()) { logFiner("All Nodes: Synchronize Foldermemberships"); } Collection<Member> connectedNodes = getController() .getNodeManager().getConnectedNodes(); for (Member node : connectedNodes) { node.synchronizeFolderMemberships(); if (canceled) { logFiner("Foldermemberships synchroniziation cancelled"); return; } } } finally { Profiling.end(pe); // Termination, remove synchronizer synchronized (folderMembershipSynchronizerLock) { // Got already new syner started in the meanwhile? if yes, // don't set to null if (folderMembershipSynchronizer == this) { folderMembershipSynchronizer = null; } } } } } }