/* * 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 java.io.File; import java.util.Enumeration; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.logging.Logger; import de.dal33t.powerfolder.ConfigurationEntry; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.light.FolderInfo; import de.dal33t.powerfolder.util.IdGenerator; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.util.StringUtils; import de.dal33t.powerfolder.util.Translation; import de.dal33t.powerfolder.util.UserDirectories; import de.dal33t.powerfolder.util.UserDirectory; import de.dal33t.powerfolder.util.Util; /** * Class to consolidate the settings for creating a folder. Used as constructor * arg for the Folder class. */ public class FolderSettings { public static final Logger LOG = Logger.getLogger(FolderSettings.class .getName()); public static final String FOLDER_SETTINGS_PREFIX_V4 = "f."; public static final String FOLDER_SETTINGS_ID = ".id"; public static final String FOLDER_SETTINGS_PREVIEW = ".preview"; public static final String FOLDER_SETTINGS_SYNC_PROFILE = ".syncprofile"; public static final String FOLDER_SETTINGS_DIR = ".dir"; public static final String FOLDER_SETTINGS_COMMIT_DIR = ".commit-dir"; public static final String FOLDER_SETTINGS_DOWNLOAD_SCRIPT = ".dlscript"; public static final String FOLDER_SETTINGS_NAME = ".name"; public static final String FOLDER_SETTINGS_VERSIONS = ".versions"; public static final String FOLDER_SETTINGS_SYNC_PATTERNS = ".sync-patterns"; public static final String FOLDER_SETTINGS_SYNC_WARN_SECONDS = ".sync-warn-seconds"; public static final String FOLDER_ID_GENERATE = "$generate"; public static final String FOLDER_ID_FROM_ACCOUNT = "$fromAccount"; /** * Field list for backup taget pre #777. Used to convert to new backup * target for #787. */ private static final String PRE_777_BACKUP_TARGET_FIELD_LIST = "true,true,true,true,0,false,12,0,m"; private static final String PRE_2040_AUTOMATIC_SYNCHRONIZATION_FIELD_LIST = "true,true,true,true,1,false,12,0,m," + Translation .getTranslation("transfer_mode.automatic_synchronization.name"); private static final String PRE_2040_AUTOMATIC_SYNCHRONIZATION_10MIN_FIELD_LIST = "true,true,true,true,10,false,12,0,m," + Translation .getTranslation("transfer_mode.automatic_synchronization_10min.name"); private static final String PRE_2074_BACKUP_SOURCE_5MIN_FIELD_LIST = "false,false,false,false,5,false,12,0,m," + Translation.getTranslation("transfer_mode.backup_source_5min.name") + ",false"; private static final String PRE_2074_BACKUP_SOURCE_HOUR_FIELD_LIST = "false,false,false,false,60,false,12,0,m," + Translation.getTranslation("transfer_mode.backup_source_hour.name") + ",false"; /** * Original value from config. */ private String localBaseDirStr; /** * Physical location of files in the folder. */ private final File localBaseDir; /** * #2056: The directory to commit/mirror the whole folder to when in reaches * 100% sync. */ private final File commitDir; /** * The synchronization profile for the folder (manual, backup, etc) */ private final SyncProfile syncProfile; /** * The number of archive versions to keep */ private final int versions; /** * Whether this sould only be a preview of the folder. */ private final boolean previewOnly; /** * #1538: Script that gets executed after a download has been completed * successfully. */ private final String downloadScript; private final boolean syncPatterns; /** * #2265: Extend monitoring. negative value = disabled, 0 = use default, * positive value = warn if folder is X seconds out of sync. */ private final int syncWarnSeconds; /** * Loaded from that config entry id. */ private String configEntryId; /** * Constructor. Creates a new FolderSettings object. NON preview, NO * download script. * * @param localBaseDir * @param syncProfile * @param createInvitationFile * @param versions */ public FolderSettings(File localBaseDir, SyncProfile syncProfile, boolean createInvitationFile, int versions) { this(localBaseDir, syncProfile, createInvitationFile, false, null, versions, true); } /** * Constructor. Creates a new FolderSettings object. * * @param localBaseDir * @param syncProfile * @param createInvitationFile * @param archiveMode * @param previewOnly * @param downloadScript * @param versions * @param syncPatterns */ public FolderSettings(File localBaseDir, SyncProfile syncProfile, boolean createInvitationFile, boolean previewOnly, String downloadScript, int versions, boolean syncPatterns) { this(localBaseDir, syncProfile, createInvitationFile, previewOnly, downloadScript, versions, syncPatterns, null, 0); } /** * Constructor. Creates a new FolderSettings object. * * @param localBaseDir * @param syncProfile * @param createInvitationFile * @param archiveMode * @param previewOnly * @param downloadScript * @param versions * @param syncPatterns * @param commitDir * @param syncWarnSeconds */ public FolderSettings(File localBaseDir, SyncProfile syncProfile, boolean createInvitationFile, boolean previewOnly, String downloadScript, int versions, boolean syncPatterns, File commitDir, int syncWarnSeconds) { Reject.ifNull(localBaseDir, "Local base dir required"); Reject.ifNull(syncProfile, "Sync profile required"); this.localBaseDir = localBaseDir; this.commitDir = commitDir; this.syncProfile = syncProfile; this.previewOnly = previewOnly; this.downloadScript = downloadScript; this.versions = versions; this.syncPatterns = syncPatterns; this.syncWarnSeconds = syncWarnSeconds; // Generate a unique entry id for config file. this.configEntryId = new String(Util.encodeHex(Util.md5(IdGenerator .makeIdBytes()))); } // ///////////// // Accessors // // ///////////// public File getLocalBaseDir() { return localBaseDir; } public File getCommitDir() { return commitDir; } /** * @return the original config entry basedir. May contain placeholder */ public String getLocalBaseDirString() { return localBaseDirStr; } public SyncProfile getSyncProfile() { return syncProfile; } public boolean isCreateInvitationFile() { return false; } public int getVersions() { return versions; } public boolean isPreviewOnly() { return previewOnly; } public String getDownloadScript() { return downloadScript; } public boolean isSyncPatterns() { return syncPatterns; } public int getSyncWarnSeconds() { return syncWarnSeconds; } /** * @return the entry id used to get/set {@link FolderSettings} to config. * e.g. FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_DIR */ public String getConfigEntryId() { return configEntryId; } /** * @param properties * @param entryId * @return */ public static String loadFolderName(Properties properties, String entryId) { Reject.ifBlank(entryId, "EntryId"); return properties.getProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_NAME); } /** * @param properties * the config properties * @return all entry ids as set. */ public static Set<String> loadEntryIds(Properties properties) { // Find all folder names. Set<String> entryIds = new TreeSet<String>(); for (@SuppressWarnings("unchecked") Enumeration<String> en = (Enumeration<String>) properties .propertyNames(); en.hasMoreElements();) { String propName = en.nextElement(); // Look for a f.<entryId>.XXXX if (propName.startsWith(FOLDER_SETTINGS_PREFIX_V4)) { int firstDot = propName.indexOf('.'); int secondDot = propName.indexOf('.', firstDot + 1); if (firstDot > 0 && secondDot > 0 && secondDot < propName.length()) { String entryId = propName .substring(firstDot + 1, secondDot); entryIds.add(entryId); } } } return entryIds; } /** * Expect something like 'f.c70001efd21928644ee14e327aa94724' or * 'f.TEST-Contacts' to remove config entries beginning with these. */ public static void removeEntries(Properties p, String entryId) { for (@SuppressWarnings("unchecked") Enumeration<String> en = (Enumeration<String>) p.propertyNames(); en .hasMoreElements();) { String propName = en.nextElement(); // Add a dot to prefix, like 'f.TEST-Contacts.', to prevent it // from also deleting things like 'f.TEST.XXXXX'. if (propName.startsWith(FOLDER_SETTINGS_PREFIX_V4 + entryId + '.')) { p.remove(propName); } } } public static FolderSettings load(Controller c, String entryId) { int defaultVersions = ConfigurationEntry.DEFAULT_ARCHIVE_VERSIONS .getValueInt(c); String defSyncProfile = ConfigurationEntry.DEFAULT_TRANSFER_MODE .getValue(c); return load(c.getConfig(), entryId, defaultVersions, defSyncProfile, true); } public static FolderSettings load(Properties properties, String entryId, int fallbackDefaultVersions, String fallbackDefaultProfile, boolean verify) { Reject.ifNull(properties, "Config"); Reject.ifBlank(entryId, "Entry Id"); String folderDirStr = properties.getProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_DIR); File folderDir = translateFolderDir(folderDirStr, verify); if (folderDir == null) { return null; } File commitDir = null; String commitDirStr = properties.getProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_COMMIT_DIR); if (StringUtils.isNotBlank(commitDirStr)) { commitDir = translateFolderDir(commitDirStr, verify); } String syncProfConfig = properties .getProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_SYNC_PROFILE); SyncProfile syncProfile; if (PRE_777_BACKUP_TARGET_FIELD_LIST.equals(syncProfConfig)) { // Migration for #787 (backup target timeBetweenScans changed // from 0 to 60). syncProfile = SyncProfile.BACKUP_TARGET; } else if (PRE_2040_AUTOMATIC_SYNCHRONIZATION_FIELD_LIST .equals(syncProfConfig) || PRE_2040_AUTOMATIC_SYNCHRONIZATION_10MIN_FIELD_LIST .equals(syncProfConfig)) { // Migration for #2040 (new auto sync uses JNotify). syncProfile = SyncProfile.AUTOMATIC_SYNCHRONIZATION; } else if (PRE_2074_BACKUP_SOURCE_5MIN_FIELD_LIST .equals(syncProfConfig) || PRE_2074_BACKUP_SOURCE_HOUR_FIELD_LIST.equals(syncProfConfig)) { // Migration for #2074 (new backup source uses JNotify). syncProfile = SyncProfile.BACKUP_SOURCE; } else if (StringUtils.isBlank(syncProfConfig)) { // Take default: syncProfile = SyncProfile .getSyncProfileByFieldList(fallbackDefaultProfile); } else { // Load profile from field list. syncProfile = SyncProfile.getSyncProfileByFieldList(syncProfConfig); } String ver = properties.getProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_VERSIONS); int versions; if (ver != null && ver.length() > 0) { versions = Integer.valueOf(ver); } else { // Take default as fallback. versions = fallbackDefaultVersions; LOG.fine("Unable to find archive settings for " + entryId + ". Using default: " + versions); } String previewSetting = properties .getProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_PREVIEW); boolean preview = previewSetting != null && "true".equalsIgnoreCase(previewSetting); String dlScript = properties.getProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_DOWNLOAD_SCRIPT); String syncPatternsSetting = properties .getProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_SYNC_PATTERNS); // Default syncPatterns to true. boolean syncPatterns = syncPatternsSetting == null || "true".equalsIgnoreCase(syncPatternsSetting); String syncWarnSecSetting = properties .getProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_SYNC_WARN_SECONDS); int syncWarnSeconds = 0; if (StringUtils.isNotBlank(syncWarnSecSetting)) { try { syncWarnSeconds = Integer.parseInt(syncWarnSecSetting); } catch (Exception e) { LOG.warning("Unable to parse sync warning settings: " + syncWarnSecSetting + ". Using default."); } } FolderSettings settings = new FolderSettings(folderDir, syncProfile, false, preview, dlScript, versions, syncPatterns, commitDir, syncWarnSeconds); settings.configEntryId = entryId; settings.localBaseDirStr = folderDirStr; return settings; } public void set(FolderInfo folderInfo, Properties config) { String entryId = this.configEntryId; if (StringUtils.isBlank(entryId)) { // Use md5 of folder id as entry id in config... entryId = new String(Util.encodeHex(Util.md5(folderInfo.id .getBytes()))); } config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_NAME, folderInfo.name); config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_ID, folderInfo.id); String baseDir = localBaseDirStr; if (StringUtils.isBlank(baseDir)) { baseDir = localBaseDir.getAbsolutePath(); } config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_DIR, baseDir); String commitDirStr = commitDir != null ? commitDir.getAbsolutePath() : ""; config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_COMMIT_DIR, commitDirStr); // Save sync profiles as internal configuration for custom profiles. config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_SYNC_PROFILE, syncProfile.getFieldList()); config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_VERSIONS, String.valueOf(versions)); config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_PREVIEW, String.valueOf(isPreviewOnly())); String dlScript = getDownloadScript() != null ? getDownloadScript() : ""; config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_DOWNLOAD_SCRIPT, dlScript); config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_SYNC_PATTERNS, String.valueOf(syncPatterns)); if (syncWarnSeconds > 0) { config.setProperty(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_SYNC_WARN_SECONDS, String.valueOf(syncWarnSeconds)); } else { config.remove(FOLDER_SETTINGS_PREFIX_V4 + entryId + FOLDER_SETTINGS_SYNC_WARN_SECONDS); } } private static File translateFolderDir(String str, boolean verify) { if (str == null) { return null; } if (!str.contains("$")) { // No placeholders found return new File(str); } String res = str; try { Map<String, UserDirectory> dirs = UserDirectories .getUserDirectories(); LOG.fine("Local placeholder directories: " + dirs); for (UserDirectory dir : dirs.values()) { if (StringUtils.isBlank(dir.getPlaceholder())) { continue; } if (res.contains(dir.getPlaceholder())) { res = res.replace(dir.getPlaceholder(), dir.getDirectory() .getAbsolutePath()); } } } catch (Exception e) { LOG.warning("Unable to translate directory path: " + str + ". " + e); } if (verify) { if (res.contains("$user.dir.") || res.contains("$apps.dir.")) { LOG.warning("Local directory for placeholders not found: " + res); return null; } if (res.contains("$")) { LOG.warning("Directory path may still contain placeholders: " + res); } } return new File(res); } }