/* * Syncany, www.syncany.org * Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel@gmail.com> * * 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/>. */ package org.syncany.config; import java.io.File; import java.util.ArrayList; import java.util.List; import org.syncany.chunk.Chunker; import org.syncany.chunk.CipherTransformer; import org.syncany.chunk.FixedChunker; import org.syncany.chunk.MultiChunker; import org.syncany.chunk.NoTransformer; import org.syncany.chunk.Transformer; import org.syncany.config.to.ConfigTO; import org.syncany.config.to.RepoTO; import org.syncany.config.to.RepoTO.MultiChunkerTO; import org.syncany.config.to.RepoTO.TransformerTO; import org.syncany.crypto.SaltedSecretKey; import org.syncany.database.DatabaseConnectionFactory; import org.syncany.database.VectorClock; import org.syncany.plugins.Plugins; import org.syncany.plugins.transfer.TransferPlugin; import org.syncany.plugins.transfer.TransferSettings; import org.syncany.util.FileUtil; import org.syncany.util.StringUtil; /** * The config class is the central point to configure a Syncany instance. It is mainly * used in the operations, but parts of it are also used in other parts of the * application -- especially file locations and names. * * <p>An instance of the <tt>Config</tt> class must be created through the transfer * objects {@link ConfigTO} and {@link RepoTO}. * * @author Philipp C. Heckel <philipp.heckel@gmail.com> */ public class Config { public static final String DIR_APPLICATION = ".syncany"; public static final String DIR_CACHE = "cache"; public static final String DIR_DATABASE = "db"; public static final String DIR_LOG = "logs"; public static final String DIR_STATE = "state"; // File in managed folder root public static final String FILE_IGNORE = ".syignore"; // Files in .syncany public static final String FILE_CONFIG = "config.xml"; public static final String FILE_REPO = "syncany"; public static final String FILE_MASTER = "master"; // File in .syncany/db public static final String FILE_DATABASE = "local.db"; // Files in .syncany/state public static final String FILE_PORT = "port.xml"; public static final String FILE_TRANSACTION = "transaction-actions.xml"; public static final String FILE_TRANSACTION_DATABASE = "transaction-database.xml"; public static final String FILE_TRANSACTION_PATTERN = "transaction-actions.%010d.xml"; public static final String FILE_TRANSACTION_DATABASE_PATTERN = "transaction-database.%010d.xml"; public static final String FILE_TRANSACTION_LIST = "transaction-list.txt"; private byte[] repoId; private String machineName; private String displayName; private File localDir; private File appDir; private File cacheDir; private File databaseDir; private File logDir; private File stateDir; private SaltedSecretKey masterKey; private Cache cache; private TransferPlugin plugin; private TransferSettings transferSettings; private Chunker chunker; private MultiChunker multiChunker; private Transformer transformer; private IgnoredFiles ignoredFiles; static { UserConfig.init(); Logging.init(); } public Config(File aLocalDir, ConfigTO configTO, RepoTO repoTO) throws ConfigException { if (aLocalDir == null || configTO == null || repoTO == null) { throw new ConfigException("Arguments aLocalDir, configTO and repoTO cannot be null."); } initNames(configTO); initMasterKey(configTO); initDirectories(aLocalDir); initCache(configTO); initIgnoredFile(); initRepo(repoTO); initConnection(configTO); } private void initNames(ConfigTO configTO) throws ConfigException { setMachineName(configTO.getMachineName()); setDisplayName(configTO.getDisplayName()); } private void initMasterKey(ConfigTO configTO) { masterKey = configTO.getMasterKey(); // can be null } private void initDirectories(File aLocalDir) throws ConfigException { localDir = FileUtil.getCanonicalFile(aLocalDir); appDir = FileUtil.getCanonicalFile(new File(localDir, DIR_APPLICATION)); cacheDir = FileUtil.getCanonicalFile(new File(appDir, DIR_CACHE)); databaseDir = FileUtil.getCanonicalFile(new File(appDir, DIR_DATABASE)); logDir = FileUtil.getCanonicalFile(new File(appDir, DIR_LOG)); stateDir = FileUtil.getCanonicalFile(new File(appDir, DIR_STATE)); } private void initCache(ConfigTO configTO) { cache = new Cache(cacheDir); if (configTO.getCacheKeepBytes() != null && configTO.getCacheKeepBytes() >= 0) { cache.setKeepBytes(configTO.getCacheKeepBytes()); } } private void initIgnoredFile() throws ConfigException { File ignoreFile = new File(localDir, FILE_IGNORE); ignoredFiles = new IgnoredFiles(ignoreFile); } private void initRepo(RepoTO repoTO) throws ConfigException { try { initRepoId(repoTO); initChunker(repoTO); initMultiChunker(repoTO); initTransformers(repoTO); } catch (Exception e) { throw new ConfigException("Unable to initialize repository information from config.", e); } } private void initRepoId(RepoTO repoTO) { repoId = repoTO.getRepoId(); } private void initChunker(RepoTO repoTO) throws Exception { // TODO [feature request] make chunking options configurable, something like described in #29 // See: https://github.com/syncany/syncany/issues/29#issuecomment-43425647 chunker = new FixedChunker(512 * 1024, "SHA1"); } private void initMultiChunker(RepoTO repoTO) throws ConfigException { MultiChunkerTO multiChunkerTO = repoTO.getMultiChunker(); if (multiChunkerTO == null) { throw new ConfigException("No multichunker in repository config."); } multiChunker = MultiChunker.getInstance(multiChunkerTO.getType()); if (multiChunker == null) { throw new ConfigException("Invalid multichunk type or settings: " + multiChunkerTO.getType()); } multiChunker.init(multiChunkerTO.getSettings()); } private void initTransformers(RepoTO repoTO) throws Exception { if (repoTO.getTransformers() == null || repoTO.getTransformers().size() == 0) { transformer = new NoTransformer(); } else { List<TransformerTO> transformerTOs = new ArrayList<TransformerTO>(repoTO.getTransformers()); Transformer lastTransformer = null; for (int i = transformerTOs.size() - 1; i >= 0; i--) { TransformerTO transformerTO = transformerTOs.get(i); Transformer transformer = Transformer.getInstance(transformerTO.getType()); if (transformer == null) { throw new ConfigException("Cannot find transformer '" + transformerTO.getType() + "'"); } if (transformer instanceof CipherTransformer) { // Dirty workaround transformerTO.getSettings().put(CipherTransformer.PROPERTY_MASTER_KEY, StringUtil.toHex(getMasterKey().getEncoded())); transformerTO.getSettings().put(CipherTransformer.PROPERTY_MASTER_KEY_SALT, StringUtil.toHex(getMasterKey().getSalt())); } transformer.init(transformerTO.getSettings()); if (lastTransformer != null) { transformer.setNextTransformer(lastTransformer); } lastTransformer = transformer; } transformer = lastTransformer; } } private void initConnection(ConfigTO configTO) throws ConfigException { if (configTO.getTransferSettings() != null) { plugin = Plugins.get(configTO.getTransferSettings().getType(), TransferPlugin.class); if (plugin == null) { throw new ConfigException("Plugin not supported: " + configTO.getTransferSettings().getType()); } try { transferSettings = configTO.getTransferSettings(); } catch (Exception e) { throw new ConfigException("Cannot initialize storage: " + e.getMessage(), e); } } } public java.sql.Connection createDatabaseConnection() { return DatabaseConnectionFactory.createConnection(getDatabaseFile(), false); } public java.sql.Connection createDatabaseConnection(boolean readOnly) { return DatabaseConnectionFactory.createConnection(getDatabaseFile(), readOnly); } public File getCacheDir() { return cacheDir; } public File getAppDir() { return appDir; } public String getMachineName() { return machineName; } public void setMachineName(String machineName) throws ConfigException { if (machineName == null || !VectorClock.MACHINE_PATTERN.matcher(machineName).matches()) { throw new ConfigException("Machine name cannot be empty and must be only characters (A-Z)."); } this.machineName = machineName; } public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } public TransferPlugin getTransferPlugin() { return plugin; } public TransferSettings getConnection() { return transferSettings; } public void setConnection(TransferSettings connection) { transferSettings = connection; } public byte[] getRepoId() { return repoId; } public Chunker getChunker() { return chunker; } public Cache getCache() { return cache; } public IgnoredFiles getIgnoredFiles() { return ignoredFiles; } public MultiChunker getMultiChunker() { return multiChunker; } public Transformer getTransformer() { return transformer; } public void setCache(Cache cache) { this.cache = cache; } public File getLocalDir() { return localDir; } public File getDatabaseDir() { return databaseDir; } public File getLogDir() { return logDir; } public File getStateDir() { return stateDir; } public SaltedSecretKey getMasterKey() { return masterKey; } public File getDatabaseFile() { return new File(databaseDir, FILE_DATABASE); } public File getPortFile() { return new File(stateDir, FILE_PORT); } public File getTransactionFile() { return new File(stateDir, FILE_TRANSACTION); } public File getTransactionDatabaseFile() { return new File(stateDir, FILE_TRANSACTION_DATABASE); } public File getTransactionListFile() { return new File(stateDir, FILE_TRANSACTION_LIST); } public File getTransactionFile(long databaseVersionNumber) { return new File(stateDir, String.format(FILE_TRANSACTION_PATTERN, databaseVersionNumber)); } public File getTransactionDatabaseFile(long databaseVersionNumber) { return new File(stateDir, String.format(FILE_TRANSACTION_DATABASE_PATTERN, databaseVersionNumber)); } }