001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.config; 019 020import java.io.File; 021import java.util.ArrayList; 022import java.util.List; 023 024import org.syncany.chunk.Chunker; 025import org.syncany.chunk.CipherTransformer; 026import org.syncany.chunk.FixedChunker; 027import org.syncany.chunk.MultiChunker; 028import org.syncany.chunk.NoTransformer; 029import org.syncany.chunk.Transformer; 030import org.syncany.config.to.ConfigTO; 031import org.syncany.config.to.RepoTO; 032import org.syncany.config.to.RepoTO.MultiChunkerTO; 033import org.syncany.config.to.RepoTO.TransformerTO; 034import org.syncany.crypto.SaltedSecretKey; 035import org.syncany.database.DatabaseConnectionFactory; 036import org.syncany.database.VectorClock; 037import org.syncany.plugins.Plugins; 038import org.syncany.plugins.transfer.TransferPlugin; 039import org.syncany.plugins.transfer.TransferSettings; 040import org.syncany.util.FileUtil; 041import org.syncany.util.StringUtil; 042 043/** 044 * The config class is the central point to configure a Syncany instance. It is mainly 045 * used in the operations, but parts of it are also used in other parts of the 046 * application -- especially file locations and names. 047 * 048 * <p>An instance of the <code>Config</code> class must be created through the transfer 049 * objects {@link ConfigTO} and {@link RepoTO}. 050 * 051 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 052 */ 053public class Config { 054 public static final String DIR_APPLICATION = ".syncany"; 055 public static final String DIR_CACHE = "cache"; 056 public static final String DIR_DATABASE = "db"; 057 public static final String DIR_LOG = "logs"; 058 public static final String DIR_STATE = "state"; 059 060 // File in managed folder root 061 public static final String FILE_IGNORE = ".syignore"; 062 063 // Files in .syncany 064 public static final String FILE_CONFIG = "config.xml"; 065 public static final String FILE_REPO = "syncany"; 066 public static final String FILE_MASTER = "master"; 067 068 // File in .syncany/db 069 public static final String FILE_DATABASE = "local.db"; 070 071 // Files in .syncany/state 072 public static final String FILE_PORT = "port.xml"; 073 public static final String FILE_TRANSACTION = "transaction-actions.xml"; 074 public static final String FILE_TRANSACTION_DATABASE = "transaction-database.xml"; 075 public static final String FILE_TRANSACTION_PATTERN = "transaction-actions.%010d.xml"; 076 public static final String FILE_TRANSACTION_DATABASE_PATTERN = "transaction-database.%010d.xml"; 077 public static final String FILE_TRANSACTION_LIST = "transaction-list.txt"; 078 079 private byte[] repoId; 080 private String machineName; 081 private String displayName; 082 private File localDir; 083 private File appDir; 084 private File cacheDir; 085 private File databaseDir; 086 private File logDir; 087 private File stateDir; 088 089 private SaltedSecretKey masterKey; 090 091 private Cache cache; 092 private TransferPlugin plugin; 093 private TransferSettings transferSettings; 094 private Chunker chunker; 095 private MultiChunker multiChunker; 096 private Transformer transformer; 097 private IgnoredFiles ignoredFiles; 098 099 static { 100 UserConfig.init(); 101 Logging.init(); 102 } 103 104 public Config(File aLocalDir, ConfigTO configTO, RepoTO repoTO) throws ConfigException { 105 if (aLocalDir == null || configTO == null || repoTO == null) { 106 throw new ConfigException("Arguments aLocalDir, configTO and repoTO cannot be null."); 107 } 108 109 initNames(configTO); 110 initMasterKey(configTO); 111 initDirectories(aLocalDir); 112 initCache(configTO); 113 initIgnoredFile(); 114 initRepo(repoTO); 115 initConnection(configTO); 116 } 117 118 private void initNames(ConfigTO configTO) throws ConfigException { 119 setMachineName(configTO.getMachineName()); 120 setDisplayName(configTO.getDisplayName()); 121 } 122 123 private void initMasterKey(ConfigTO configTO) { 124 masterKey = configTO.getMasterKey(); // can be null 125 } 126 127 private void initDirectories(File aLocalDir) throws ConfigException { 128 localDir = FileUtil.getCanonicalFile(aLocalDir); 129 appDir = FileUtil.getCanonicalFile(new File(localDir, DIR_APPLICATION)); 130 cacheDir = FileUtil.getCanonicalFile(new File(appDir, DIR_CACHE)); 131 databaseDir = FileUtil.getCanonicalFile(new File(appDir, DIR_DATABASE)); 132 logDir = FileUtil.getCanonicalFile(new File(appDir, DIR_LOG)); 133 stateDir = FileUtil.getCanonicalFile(new File(appDir, DIR_STATE)); 134 } 135 136 private void initCache(ConfigTO configTO) { 137 cache = new Cache(cacheDir); 138 139 if (configTO.getCacheKeepBytes() != null && configTO.getCacheKeepBytes() >= 0) { 140 cache.setKeepBytes(configTO.getCacheKeepBytes()); 141 } 142 } 143 144 private void initIgnoredFile() throws ConfigException { 145 File ignoreFile = new File(localDir, FILE_IGNORE); 146 ignoredFiles = new IgnoredFiles(ignoreFile); 147 } 148 149 private void initRepo(RepoTO repoTO) throws ConfigException { 150 try { 151 initRepoId(repoTO); 152 initChunker(repoTO); 153 initMultiChunker(repoTO); 154 initTransformers(repoTO); 155 } 156 catch (Exception e) { 157 throw new ConfigException("Unable to initialize repository information from config.", e); 158 } 159 } 160 161 private void initRepoId(RepoTO repoTO) { 162 repoId = repoTO.getRepoId(); 163 } 164 165 private void initChunker(RepoTO repoTO) throws Exception { 166 // TODO [feature request] make chunking options configurable, something like described in #29 167 // See: https://github.com/syncany/syncany/issues/29#issuecomment-43425647 168 169 chunker = new FixedChunker(512 * 1024, "SHA1"); 170 } 171 172 private void initMultiChunker(RepoTO repoTO) throws ConfigException { 173 MultiChunkerTO multiChunkerTO = repoTO.getMultiChunker(); 174 175 if (multiChunkerTO == null) { 176 throw new ConfigException("No multichunker in repository config."); 177 } 178 179 multiChunker = MultiChunker.getInstance(multiChunkerTO.getType()); 180 181 if (multiChunker == null) { 182 throw new ConfigException("Invalid multichunk type or settings: " + multiChunkerTO.getType()); 183 } 184 185 multiChunker.init(multiChunkerTO.getSettings()); 186 } 187 188 private void initTransformers(RepoTO repoTO) throws Exception { 189 if (repoTO.getTransformers() == null || repoTO.getTransformers().size() == 0) { 190 transformer = new NoTransformer(); 191 } 192 else { 193 List<TransformerTO> transformerTOs = new ArrayList<TransformerTO>(repoTO.getTransformers()); 194 Transformer lastTransformer = null; 195 196 for (int i = transformerTOs.size() - 1; i >= 0; i--) { 197 TransformerTO transformerTO = transformerTOs.get(i); 198 Transformer transformer = Transformer.getInstance(transformerTO.getType()); 199 200 if (transformer == null) { 201 throw new ConfigException("Cannot find transformer '" + transformerTO.getType() + "'"); 202 } 203 204 if (transformer instanceof CipherTransformer) { // Dirty workaround 205 transformerTO.getSettings().put(CipherTransformer.PROPERTY_MASTER_KEY, StringUtil.toHex(getMasterKey().getEncoded())); 206 transformerTO.getSettings().put(CipherTransformer.PROPERTY_MASTER_KEY_SALT, StringUtil.toHex(getMasterKey().getSalt())); 207 } 208 209 transformer.init(transformerTO.getSettings()); 210 211 if (lastTransformer != null) { 212 transformer.setNextTransformer(lastTransformer); 213 } 214 215 lastTransformer = transformer; 216 } 217 218 transformer = lastTransformer; 219 } 220 } 221 222 private void initConnection(ConfigTO configTO) throws ConfigException { 223 if (configTO.getTransferSettings() != null) { 224 plugin = Plugins.get(configTO.getTransferSettings().getType(), TransferPlugin.class); 225 226 if (plugin == null) { 227 throw new ConfigException("Plugin not supported: " + configTO.getTransferSettings().getType()); 228 } 229 230 try { 231 transferSettings = configTO.getTransferSettings(); 232 } 233 catch (Exception e) { 234 throw new ConfigException("Cannot initialize storage: " + e.getMessage(), e); 235 } 236 } 237 } 238 239 public java.sql.Connection createDatabaseConnection() { 240 return DatabaseConnectionFactory.createConnection(getDatabaseFile(), false); 241 } 242 243 public java.sql.Connection createDatabaseConnection(boolean readOnly) { 244 return DatabaseConnectionFactory.createConnection(getDatabaseFile(), readOnly); 245 } 246 247 public File getCacheDir() { 248 return cacheDir; 249 } 250 251 public File getAppDir() { 252 return appDir; 253 } 254 255 public String getMachineName() { 256 return machineName; 257 } 258 259 public void setMachineName(String machineName) throws ConfigException { 260 if (machineName == null || !VectorClock.MACHINE_PATTERN.matcher(machineName).matches()) { 261 throw new ConfigException("Machine name cannot be empty and must be only characters (A-Z)."); 262 } 263 264 this.machineName = machineName; 265 } 266 267 public String getDisplayName() { 268 return displayName; 269 } 270 271 public void setDisplayName(String displayName) { 272 this.displayName = displayName; 273 } 274 275 public TransferPlugin getTransferPlugin() { 276 return plugin; 277 } 278 279 public TransferSettings getConnection() { 280 return transferSettings; 281 } 282 283 public void setConnection(TransferSettings connection) { 284 transferSettings = connection; 285 } 286 287 public byte[] getRepoId() { 288 return repoId; 289 } 290 291 public Chunker getChunker() { 292 return chunker; 293 } 294 295 public Cache getCache() { 296 return cache; 297 } 298 299 public IgnoredFiles getIgnoredFiles() { 300 return ignoredFiles; 301 } 302 303 public MultiChunker getMultiChunker() { 304 return multiChunker; 305 } 306 307 public Transformer getTransformer() { 308 return transformer; 309 } 310 311 public void setCache(Cache cache) { 312 this.cache = cache; 313 } 314 315 public File getLocalDir() { 316 return localDir; 317 } 318 319 public File getDatabaseDir() { 320 return databaseDir; 321 } 322 323 public File getLogDir() { 324 return logDir; 325 } 326 327 public File getStateDir() { 328 return stateDir; 329 } 330 331 public SaltedSecretKey getMasterKey() { 332 return masterKey; 333 } 334 335 public File getDatabaseFile() { 336 return new File(databaseDir, FILE_DATABASE); 337 } 338 339 public File getPortFile() { 340 return new File(stateDir, FILE_PORT); 341 } 342 343 public File getTransactionFile() { 344 return new File(stateDir, FILE_TRANSACTION); 345 } 346 347 public File getTransactionDatabaseFile() { 348 return new File(stateDir, FILE_TRANSACTION_DATABASE); 349 } 350 351 public File getTransactionListFile() { 352 return new File(stateDir, FILE_TRANSACTION_LIST); 353 } 354 355 public File getTransactionFile(long databaseVersionNumber) { 356 return new File(stateDir, String.format(FILE_TRANSACTION_PATTERN, databaseVersionNumber)); 357 } 358 359 public File getTransactionDatabaseFile(long databaseVersionNumber) { 360 return new File(stateDir, String.format(FILE_TRANSACTION_DATABASE_PATTERN, databaseVersionNumber)); 361 } 362}