[syncany] 07/14: Imported Upstream version 0.4.7~alpha

Markus Koschany apo-guest at moszumanska.debian.org
Wed Nov 11 22:58:48 UTC 2015


This is an automated email from the git hooks/post-receive script.

apo-guest pushed a commit to branch master
in repository syncany.

commit 0643c60c85d5bd5c44f2acfa42ecd974ffa4d4ff
Author: Markus Koschany <apo at debian.org>
Date:   Wed Nov 11 22:51:40 2015 +0100

    Imported Upstream version 0.4.7~alpha
---
 .gitignore                                         |   1 +
 CHANGELOG.md                                       |  11 +
 build.gradle                                       |   2 +-
 gradle/arch/syncany/PKGBUILD                       |   6 +-
 gradle/daemon/syncanyd.bat.skel                    |   8 +-
 gradle/gradle/application.distribution.gradle      |   6 +-
 gradle/innosetup/innoinstall.sh                    |   5 +-
 gradle/osx/syncany.rb                              |  11 +-
 .../main/java/org/syncany/cli/PluginCommand.java   |   2 +-
 .../operations/down/DatabaseFileReader.java        | 160 +++++++++++++
 .../org/syncany/operations/down/DownOperation.java | 126 ++--------
 .../down/FileSystemActionReconciliator.java        |  15 +-
 .../down/actions/ChangeFileSystemAction.java       |   6 +-
 .../down/actions/FileCreatingFileSystemAction.java |  11 +-
 .../down/actions/NewFileSystemAction.java          |   5 +-
 .../syncany/operations/init/ConnectOperation.java  |   2 +-
 .../restore/RestoreFileSystemAction.java           |   5 +-
 .../operations/restore/RestoreOperation.java       |   6 +-
 .../org/syncany/operations/up/UpOperation.java     | 256 +++++++++++----------
 .../plugins/local/LocalTransferManager.java        |   2 +-
 .../TransactionAwareFeatureTransferManager.java    |  11 +-
 .../java/org/syncany/tests/ScenarioTestSuite.java  |   4 +
 .../operations/FileSystemActionComparatorTest.java |   2 +-
 .../Issue520NoResumeOnCorruptXmlScenarioTest.java  |  77 +++++++
 24 files changed, 474 insertions(+), 266 deletions(-)

diff --git a/.gitignore b/.gitignore
index 7f49107..857f604 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ gradle/arch/syncany/pkg
 gradle/arch/syncany/src
 gradle/arch/syncany/syncany*.tar*
 *.orig
+.recommenders
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4150ba8..69f76cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,17 @@
 Change Log
 ==========
 
+### Syncany 0.4.7-alpha (Date: 7 Nov 2015)
+- Developer/alpha release (**We are now nearing the beta phase. Stay tuned!**)
+- Bugfixes and other things:
+  + Refactoring and simplification of UpOperation
+  + Refactoring DownOperation (better memory management)
+  + Refactoring of error handling (don't throw 'Exception')
+  + Fix GUI crashes in 'Add folder' wizard #497
+  + Fix OSX daemon start/stop to new style #281/#530
+  + Fix Windows spaces in path issue #522/#529
+  + Fix not resuming transactions if transaction files are corrupt #520
+
 ### Syncany 0.4.6-alpha (Date: 11 July 2015)
 - Developer/alpha release (**We are now nearing the beta phase. Stay tuned!**)
 - Features and significant changes:
diff --git a/build.gradle b/build.gradle
index b7b0a02..0b79219 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ apply from: 'gradle/gradle/helpers.gradle'
 // Global Settings /////////////////////////////////////////////////////////////
 
 project.ext {
-	applicationVersion = "0.4.6-alpha"
+	applicationVersion = "0.4.7-alpha"
 	applicationVersionDebian = "1";
 
 	applicationRelease = isApplicationRelease()
diff --git a/gradle/arch/syncany/PKGBUILD b/gradle/arch/syncany/PKGBUILD
index f8ae744..02af931 100644
--- a/gradle/arch/syncany/PKGBUILD
+++ b/gradle/arch/syncany/PKGBUILD
@@ -1,7 +1,7 @@
 # Maintainer: Pim Otte <otte dot pim at gmail dot com>
 pkgname=syncany
-pkgver=0.4.5_alpha
-_realver=0.4.5-alpha
+pkgver=0.4.6_alpha
+_realver=0.4.6-alpha
 pkgrel=1
 pkgdesc="Cloud storage and filesharing application with a focus on security and abstraction of storage."
 arch=(any)
@@ -10,7 +10,7 @@ license=('GPL3')
 depends=('java-runtime>=7' 'bash-completion')
 source=("http://syncany.org/dist/$pkgname-${_realver}.tar.gz"
         )
-sha256sums=('7e8fbad0d9bf90369ad21fd1b3d54c7025738f2da9b1a364273f16cb76d59069')
+sha256sums=('9aab83cc336b898a48dde5e7799d703ef569255e5c6f24a7182a6983c0846bc8')
 
 package() {
     install -Dm644 "$srcdir/$pkgname-${_realver}/bash/syncany.bash-completion" "${pkgdir}/etc/bash_completion.d/syncany"
diff --git a/gradle/daemon/syncanyd.bat.skel b/gradle/daemon/syncanyd.bat.skel
index f972758..a3ef48d 100644
--- a/gradle/daemon/syncanyd.bat.skel
+++ b/gradle/daemon/syncanyd.bat.skel
@@ -1,10 +1,10 @@
 rem Embedded Daemon script begins ################################SYNCANY_INCL_3#
 
 set APP_NAME=syncanyd
-set APP_USER_DIR=%AppData%\Syncany
-set APP_DAEMON_CONTROL=%APP_USER_DIR%\daemon.ctrl
-set APP_DAEMON_PIDFILE=%APP_USER_DIR%\daemon.pid
-set APP_DAEMON_PIDFILE_TMP=%APP_USER_DIR%\daemon.pid.tmp
+set APP_USER_DIR="%AppData%\Syncany"
+set APP_DAEMON_CONTROL="%APP_USER_DIR%\daemon.ctrl"
+set APP_DAEMON_PIDFILE="%APP_USER_DIR%\daemon.pid"
+set APP_DAEMON_PIDFILE_TMP="%APP_USER_DIR%\daemon.pid.tmp"
 
 if not exist "%APP_USER_DIR%" mkdir "%APP_USER_DIR%" 
 
diff --git a/gradle/gradle/application.distribution.gradle b/gradle/gradle/application.distribution.gradle
index 3d47eeb..ea70a91 100644
--- a/gradle/gradle/application.distribution.gradle
+++ b/gradle/gradle/application.distribution.gradle
@@ -22,7 +22,7 @@ startScripts {
 
 		// - Read max memory (-Xmx) from userconfig.xml
 		String winMaxMemoryCommands = "@rem Read max memory from userconfig.xml #SYNCANY_INCL_1#\r\n"
-		winMaxMemoryCommands += "set APP_USERCONFIG_FILE=%AppData%\\\\Syncany\\\\userconfig.xml\r\n";
+		winMaxMemoryCommands += "set APP_USERCONFIG_FILE=\"%AppData%\\\\Syncany\\\\userconfig.xml\"\r\n";
 		winMaxMemoryCommands += "\r\n";
 		winMaxMemoryCommands += "if exist \"%APP_USERCONFIG_FILE%\" (\r\n";
 		winMaxMemoryCommands += "  if \"%OS%\"==\"Windows_NT\" setlocal ENABLEDELAYEDEXPANSION\r\n";
@@ -42,7 +42,7 @@ startScripts {
 
 		// - Post Java process commands: Delayed plugin JAR file deletion (Windows only)
 		String winPurgeFileDeletionCommands = "@rem Delete plugin JARs #SYNCANY_INCL_2#\r\n"
-		winPurgeFileDeletionCommands += "SET PURGEFILE=%AppData%\\\\Syncany\\\\purgefile\r\n";
+		winPurgeFileDeletionCommands += "SET PURGEFILE=\"%AppData%\\\\Syncany\\\\purgefile\"\r\n";
 		winPurgeFileDeletionCommands += "if exist %PURGEFILE% (\r\n";
 		winPurgeFileDeletionCommands += "  @for /f %%b in (%PURGEFILE%) do del /q \"%%b\" 2>NUL\r\n";
 		winPurgeFileDeletionCommands += "  del /q %PURGEFILE% 2>NUL\r\n";
@@ -52,7 +52,7 @@ startScripts {
 
 		// - Post Java process commands: Delayed plugin JAR file install (Windows only)
 		String winUpdateFileCommands = "@rem Reinstall plugins after update removal #SYNCANY_INCL_4#\r\n"
-		winUpdateFileCommands += "SET UPDATEFILE=%AppData%\\\\Syncany\\\\updatefile\r\n";
+		winUpdateFileCommands += "SET UPDATEFILE=\"%AppData%\\\\Syncany\\\\updatefile\"\r\n";
 		winUpdateFileCommands += "if exist %UPDATEFILE% (\r\n";
 		winUpdateFileCommands += "  @for /f %%b in (%UPDATEFILE%) do (\r\n    <nul set /p =Updating %%b... \r\n    \"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %SYNCANY_OPTS%  -classpath \"%CLASSPATH%\" org.syncany.Syncany plugin install %%b -m 2>NUL)\r\n";
 		winUpdateFileCommands += "  del /q %UPDATEFILE% 2>NUL\r\n";
diff --git a/gradle/innosetup/innoinstall.sh b/gradle/innosetup/innoinstall.sh
index 44f4a8d..90a960f 100755
--- a/gradle/innosetup/innoinstall.sh
+++ b/gradle/innosetup/innoinstall.sh
@@ -4,11 +4,8 @@ rm -rf /tmp/inno
 mkdir /tmp/inno
 cd /tmp/inno
 
-wget -O is.exe http://www.jrsoftware.org/download.php/is.exe
+wget -O is.exe http://files.jrsoftware.org/is/5/isetup-5.5.5.exe
 innoextract is.exe
 mkdir -p ~/".wine/drive_c/inno"
 cp -a app/* ~/".wine/drive_c/inno"
 
-#mkdir ~/".wine/drive_c/Program Files (x86)/Inno Setup 5"
-#cp -a app/* ~/".wine/drive_c/Program Files (x86)/Inno Setup 5"
-
diff --git a/gradle/osx/syncany.rb b/gradle/osx/syncany.rb
index 15a8fb4..e5b2952 100644
--- a/gradle/osx/syncany.rb
+++ b/gradle/osx/syncany.rb
@@ -2,9 +2,9 @@ require "formula"
 
 class Syncany < Formula
   homepage "https://www.syncany.org"
-  url "https://codeload.github.com/syncany/syncany/tar.gz/v0.4.5-alpha"
-  sha256 "957537a7177a5234e794d871043e67ac8e06ff3ff1ee059e599dfd477c5bb8e5"
-  version "0.4.5-alpha"
+  url "https://codeload.github.com/syncany/syncany/tar.gz/v0.4.6-alpha"
+  sha256 "1dfd92e7618297eae6ee0d8acb7103f55c8d18500da409d032a5941147fbcd85"
+  version "0.4.6-alpha"
   head "https://github.com/syncany/syncany.git", :branch => "develop"
 
   depends_on :java => "1.7+"
@@ -26,7 +26,7 @@ class Syncany < Formula
     bin.install_symlink Dir["#{libexec}/bin/sy"]
   end
 
-  plist_options :manual => "sy --daemon"
+  plist_options :manual => "sy daemon start"
 
   def plist; <<-EOS.undent
     <?xml version="1.0" encoding="UTF-8"?>
@@ -38,7 +38,8 @@ class Syncany < Formula
       <key>ProgramArguments</key>
       <array>
         <string>#{prefix}/bin/sy</string>
-        <string>--daemon</string>
+        <string>daemon</string>
+        <string>start</string>
       </array>
       <key>ProcessType</key>
       <string>Background</string>
diff --git a/syncany-cli/src/main/java/org/syncany/cli/PluginCommand.java b/syncany-cli/src/main/java/org/syncany/cli/PluginCommand.java
index 1972c94..86c4d55 100644
--- a/syncany-cli/src/main/java/org/syncany/cli/PluginCommand.java
+++ b/syncany-cli/src/main/java/org/syncany/cli/PluginCommand.java
@@ -225,7 +225,7 @@ public class PluginCommand extends Command {
 			
 			if (thirdPartyCount > 0) {
 				String pluginPlugins = (thirdPartyCount == 1) ? "plugin" : "plugins";				
-				out.printf("\nThird party plugins:\nPlease note that the Syncany Team does not take review or maintain the third-party %s\nlisted above. Please report issues to the corresponding plugin site.\n", pluginPlugins);
+				out.printf("\nThird party plugins:\nPlease note that the Syncany Team does not review or maintain the third-party %s\nlisted above. Please report issues to the corresponding plugin site.\n", pluginPlugins);
 			}
 		}
 		else {
diff --git a/syncany-lib/src/main/java/org/syncany/operations/down/DatabaseFileReader.java b/syncany-lib/src/main/java/org/syncany/operations/down/DatabaseFileReader.java
new file mode 100644
index 0000000..8d5363c
--- /dev/null
+++ b/syncany-lib/src/main/java/org/syncany/operations/down/DatabaseFileReader.java
@@ -0,0 +1,160 @@
+/*
+ * Syncany, www.syncany.org
+ * Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel at 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.operations.down;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.syncany.database.DatabaseVersion;
+import org.syncany.database.DatabaseVersionHeader;
+import org.syncany.database.MemoryDatabase;
+import org.syncany.database.VectorClock;
+import org.syncany.database.dao.DatabaseXmlSerializer;
+import org.syncany.database.dao.DatabaseXmlSerializer.DatabaseReadType;
+
+/**
+ * The DatabaseFileReader provides a way to read a series of database files
+ * in a memory-efficient way, by converting them to a series of MemoryDatabases,
+ * none of which are too large.
+ * 
+ * @author Pim Otte
+ */
+public class DatabaseFileReader implements Iterator<MemoryDatabase> {
+	private static final int MAX_FILES = 9999;
+
+	private DatabaseXmlSerializer databaseSerializer;
+	private List<DatabaseVersionHeader> winnersApplyBranchList;
+	private Map<DatabaseVersionHeader, File> databaseVersionLocations;
+	private int branchIndex = 0;
+
+	public DatabaseFileReader(DatabaseXmlSerializer databaseSerializer, DatabaseBranch winnersApplyBranch,
+			Map<DatabaseVersionHeader, File> databaseVersionLocations) {
+		
+		this.winnersApplyBranchList = winnersApplyBranch.getAll();
+		this.databaseVersionLocations = databaseVersionLocations;
+		this.databaseSerializer = databaseSerializer;
+	}
+
+	public boolean hasNext() {
+		return branchIndex < winnersApplyBranchList.size();
+	}
+
+	/**
+	 * Loads the winner's database branch into the memory in a {@link MemoryDatabase} object, by using
+	 * the already downloaded list of remote database files.
+	 *
+	 * <p>Because database files can contain multiple {@link DatabaseVersion}s per client, a range for which
+	 * to load the database versions must be determined.
+	 *
+	 * <p><b>Example 1:</b><br />
+	 * <pre>
+	 *  db-A-0001   (A1)     Already known             Not loaded
+	 *  db-A-0005   (A2)     Already known             Not loaded
+	 *              (A3)     Already known             Not loaded
+	 *              (A4)     Part of winner's branch   Loaded
+	 *              (A5)     Purge database version    Ignored (only DEFAULT)
+	 *  db-B-0001   (A5,B1)  Part of winner's branch   Loaded
+	 *  db-A-0006   (A6,B1)  Part of winner's branch   Loaded
+	 * </pre>
+	 *
+	 * <p>In example 1, only (A4)-(A5) must be loaded from db-A-0005, and not all four database versions.
+	 *
+	 * <p><b>Other example:</b><br />
+	 * <pre>
+	 *  db-A-0005   (A1)     Part of winner's branch   Loaded
+	 *  db-A-0005   (A2)     Part of winner's branch   Loaded
+	 *  db-B-0001   (A2,B1)  Part of winner's branch   Loaded
+	 *  db-A-0005   (A3,B1)  Part of winner's branch   Loaded
+	 *  db-A-0005   (A4,B1)  Part of winner's branch   Loaded
+	 *  db-A-0005   (A5,B1)  Purge database version    Ignored (only DEFAULT)
+	 * </pre>
+	 *
+	 * <p>In example 2, (A1)-(A5,B1) [except (A2,B1)] are contained in db-A-0005 (after merging!), so
+	 * db-A-0005 must be processed twice; each time loading separate parts of the file. In this case:
+	 * First load (A1)-(A2) from db-A-0005, then load (A2,B1) from db-B-0001, then load (A3,B1)-(A4,B1)
+	 * from db-A-0005, and ignore (A5,B1).
+	 * @param databaseFileList
+	 * @param ignoredMostRecentPurgeVersions
+	 *
+	 * @return Returns a loaded memory database containing all metadata from the winner's branch
+	 */
+	@Override
+	public MemoryDatabase next() {
+		MemoryDatabase winnerBranchDatabase = new MemoryDatabase();
+		String rangeClientName = null;
+		VectorClock rangeVersionFrom = null;
+		VectorClock rangeVersionTo = null;
+
+		while (branchIndex < winnersApplyBranchList.size() && winnerBranchDatabase.getFileHistories().size() < MAX_FILES) {
+			DatabaseVersionHeader currentDatabaseVersionHeader = winnersApplyBranchList.get(branchIndex);
+			DatabaseVersionHeader nextDatabaseVersionHeader = (branchIndex + 1 < winnersApplyBranchList.size()) ? winnersApplyBranchList
+					.get(branchIndex + 1) : null;
+
+			// First of range for this client
+			if (rangeClientName == null) {
+				rangeClientName = currentDatabaseVersionHeader.getClient();
+				rangeVersionFrom = currentDatabaseVersionHeader.getVectorClock();
+				rangeVersionTo = currentDatabaseVersionHeader.getVectorClock();
+			}
+
+			// Still in range for this client
+			else {
+				rangeVersionTo = currentDatabaseVersionHeader.getVectorClock();
+			}
+
+			// Now load this stuff from the database file (or not)
+			//   - If the database file exists, load the range and reset it
+			//   - If not, only force a load if this is the range end
+
+			File databaseVersionFile = databaseVersionLocations.get(currentDatabaseVersionHeader);
+
+			if (databaseVersionFile == null) {
+				throw new RuntimeException("Could not find file corresponding to " + currentDatabaseVersionHeader
+						+ ", while it is in the winners branch.");
+			}
+
+			boolean lastDatabaseVersionHeader = nextDatabaseVersionHeader == null;
+			boolean nextDatabaseVersionInSameFile = lastDatabaseVersionHeader
+					|| databaseVersionFile.equals(databaseVersionLocations.get(nextDatabaseVersionHeader));
+			boolean rangeEnds = lastDatabaseVersionHeader || !nextDatabaseVersionInSameFile;
+
+			if (rangeEnds) {
+				try {
+					databaseSerializer.load(winnerBranchDatabase, databaseVersionFile, rangeVersionFrom, rangeVersionTo, DatabaseReadType.FULL);
+				}
+				catch (IOException e) {
+					throw new RuntimeException(e.getMessage(), e);
+				}
+				rangeClientName = null;
+			}
+			branchIndex++;
+		}
+
+		return winnerBranchDatabase;
+	}
+
+	@Override
+	public void remove() {
+		throw new UnsupportedOperationException("Removing a databaseversion is not supported");
+
+	}
+
+}
diff --git a/syncany-lib/src/main/java/org/syncany/operations/down/DownOperation.java b/syncany-lib/src/main/java/org/syncany/operations/down/DownOperation.java
index 2735aac..4849eca 100644
--- a/syncany-lib/src/main/java/org/syncany/operations/down/DownOperation.java
+++ b/syncany-lib/src/main/java/org/syncany/operations/down/DownOperation.java
@@ -457,119 +457,43 @@ public class DownOperation extends AbstractTransferOperation {
 		}
 		else {
 			logger.log(Level.INFO, "Loading winners database (DEFAULT) ...");
-			MemoryDatabase winnersDatabase = readWinnersDatabase(winnersApplyBranch, databaseVersionLocations);
+			DatabaseFileReader databaseFileReader = new DatabaseFileReader(databaseSerializer, winnersApplyBranch, databaseVersionLocations);
 
-			if (options.isApplyChanges()) {
-				new ApplyChangesOperation(config, localDatabase, transferManager, winnersDatabase, result, cleanupOccurred,
-						preDeleteFileHistoriesWithLastVersion).execute();
-			}
+			boolean noDatabaseVersions = !databaseFileReader.hasNext();
+			
+			if (noDatabaseVersions) {
+				applyChangesAndPersistDatabase(new MemoryDatabase(), cleanupOccurred, preDeleteFileHistoriesWithLastVersion);
+			} 
 			else {
-				logger.log(Level.INFO, "Doing nothing on the file system, because --no-apply switched on");
+				while (databaseFileReader.hasNext()) {
+					MemoryDatabase winnersDatabase = databaseFileReader.next();
+					applyChangesAndPersistDatabase(winnersDatabase, cleanupOccurred, preDeleteFileHistoriesWithLastVersion);					
+				}
 			}
 
-			persistDatabaseVersions(winnersApplyBranch, winnersDatabase);
-
 			result.setResultCode(DownResultCode.OK_WITH_REMOTE_CHANGES);
 		}
 	}
 
-	/**
-	 * Loads the winner's database branch into the memory in a {@link MemoryDatabase} object, by using
-	 * the already downloaded list of remote database files.
-	 *
-	 * <p>Because database files can contain multiple {@link DatabaseVersion}s per client, a range for which
-	 * to load the database versions must be determined.
-	 *
-	 * <p><b>Example 1:</b><br />
-	 * <pre>
-	 *  db-A-0001   (A1)     Already known             Not loaded
-	 *  db-A-0005   (A2)     Already known             Not loaded
-	 *              (A3)     Already known             Not loaded
-	 *              (A4)     Part of winner's branch   Loaded
-	 *              (A5)     Purge database version    Ignored (only DEFAULT)
-	 *  db-B-0001   (A5,B1)  Part of winner's branch   Loaded
-	 *  db-A-0006   (A6,B1)  Part of winner's branch   Loaded
-	 * </pre>
-	 *
-	 * <p>In example 1, only (A4)-(A5) must be loaded from db-A-0005, and not all four database versions.
-	 *
-	 * <p><b>Other example:</b><br />
-	 * <pre>
-	 *  db-A-0005   (A1)     Part of winner's branch   Loaded
-	 *  db-A-0005   (A2)     Part of winner's branch   Loaded
-	 *  db-B-0001   (A2,B1)  Part of winner's branch   Loaded
-	 *  db-A-0005   (A3,B1)  Part of winner's branch   Loaded
-	 *  db-A-0005   (A4,B1)  Part of winner's branch   Loaded
-	 *  db-A-0005   (A5,B1)  Purge database version    Ignored (only DEFAULT)
-	 * </pre>
-	 *
-	 * <p>In example 2, (A1)-(A5,B1) [except (A2,B1)] are contained in db-A-0005 (after merging!), so
-	 * db-A-0005 must be processed twice; each time loading separate parts of the file. In this case:
-	 * First load (A1)-(A2) from db-A-0005, then load (A2,B1) from db-B-0001, then load (A3,B1)-(A4,B1)
-	 * from db-A-0005, and ignore (A5,B1).
-	 * @param databaseFileList
-	 * @param ignoredMostRecentPurgeVersions
-	 *
-	 * @return Returns a loaded memory database containing all metadata from the winner's branch
-	 */
-	private MemoryDatabase readWinnersDatabase(DatabaseBranch winnersApplyBranch, Map<DatabaseVersionHeader, File> databaseVersionLocations)
-			throws IOException, StorageException {
-
-		MemoryDatabase winnerBranchDatabase = new MemoryDatabase();
-
-		List<DatabaseVersionHeader> winnersApplyBranchList = winnersApplyBranch.getAll();
-
-		String rangeClientName = null;
-		VectorClock rangeVersionFrom = null;
-		VectorClock rangeVersionTo = null;
-
-		for (int i = 0; i < winnersApplyBranchList.size(); i++) {
-			DatabaseVersionHeader currentDatabaseVersionHeader = winnersApplyBranchList.get(i);
-			DatabaseVersionHeader nextDatabaseVersionHeader = (i + 1 < winnersApplyBranchList.size()) ? winnersApplyBranchList.get(i + 1) : null;
-
-			// First of range for this client
-			if (rangeClientName == null) {
-				rangeClientName = currentDatabaseVersionHeader.getClient();
-				rangeVersionFrom = currentDatabaseVersionHeader.getVectorClock();
-				rangeVersionTo = currentDatabaseVersionHeader.getVectorClock();
-			}
-
-			// Still in range for this client
-			else {
-				rangeVersionTo = currentDatabaseVersionHeader.getVectorClock();
-			}
-
-			// Now load this stuff from the database file (or not)
-			//   - If the database file exists, load the range and reset it
-			//   - If not, only force a load if this is the range end
-
-			File databaseVersionFile = databaseVersionLocations.get(currentDatabaseVersionHeader);
-
-			if (databaseVersionFile == null) {
-				throw new StorageException("Could not find file corresponding to " + currentDatabaseVersionHeader
-						+ ", while it is in the winners branch.");
-			}
-
-			boolean lastDatabaseVersionHeader = nextDatabaseVersionHeader == null;
-			boolean nextDatabaseVersionInSameFile = lastDatabaseVersionHeader
-					|| databaseVersionFile.equals(databaseVersionLocations.get(nextDatabaseVersionHeader));
-			boolean rangeEnds = lastDatabaseVersionHeader || !nextDatabaseVersionInSameFile;
-
-			if (rangeEnds) {
-				databaseSerializer.load(winnerBranchDatabase, databaseVersionFile, rangeVersionFrom, rangeVersionTo, DatabaseReadType.FULL);
-				rangeClientName = null;
-			}
+	private void applyChangesAndPersistDatabase(MemoryDatabase winnersDatabase, boolean cleanupOccurred, 
+			List<PartialFileHistory> preDeleteFileHistoriesWithLastVersion) throws Exception {
+		
+		if (options.isApplyChanges()) {
+			new ApplyChangesOperation(config, localDatabase, transferManager, winnersDatabase, result, cleanupOccurred,
+					preDeleteFileHistoriesWithLastVersion).execute();
+		}
+		else {
+			logger.log(Level.INFO, "Doing nothing on the file system, because --no-apply switched on");
 		}
 
-		if (logger.isLoggable(Level.FINE)) {
-			logger.log(Level.FINE, "Winner Database Branch:");
-
-			for (DatabaseVersion dbv : winnerBranchDatabase.getDatabaseVersions()) {
-				logger.log(Level.FINE, "- " + dbv.getHeader());
-			}
+		// We only persist the versions that we have already applied.
+		DatabaseBranch currentApplyBranch = new DatabaseBranch();
+		for (DatabaseVersion databaseVersion : winnersDatabase.getDatabaseVersions()) {
+			currentApplyBranch.add(databaseVersion.getHeader());
 		}
 
-		return winnerBranchDatabase;
+		persistDatabaseVersions(currentApplyBranch, winnersDatabase);
+		localDatabase.commit();
 	}
 
 	/**
diff --git a/syncany-lib/src/main/java/org/syncany/operations/down/FileSystemActionReconciliator.java b/syncany-lib/src/main/java/org/syncany/operations/down/FileSystemActionReconciliator.java
index c01d2ef..093dc86 100644
--- a/syncany-lib/src/main/java/org/syncany/operations/down/FileSystemActionReconciliator.java
+++ b/syncany-lib/src/main/java/org/syncany/operations/down/FileSystemActionReconciliator.java
@@ -36,6 +36,7 @@ import org.syncany.database.MemoryDatabase;
 import org.syncany.database.PartialFileHistory;
 import org.syncany.database.PartialFileHistory.FileHistoryId;
 import org.syncany.database.SqlDatabase;
+import org.syncany.operations.Assembler;
 import org.syncany.operations.ChangeSet;
 import org.syncany.operations.down.actions.ChangeFileSystemAction;
 import org.syncany.operations.down.actions.DeleteFileSystemAction;
@@ -132,6 +133,7 @@ public class FileSystemActionReconciliator {
 	private ChangeSet changeSet;
 	private SqlDatabase localDatabase;
 	private FileVersionComparator fileVersionComparator;
+	private Assembler assembler;
 	
 	public FileSystemActionReconciliator(Config config, ChangeSet changeSet) {
 		this.config = config; 
@@ -147,6 +149,7 @@ public class FileSystemActionReconciliator {
 
 	public List<FileSystemAction> determineFileSystemActions(MemoryDatabase winnersDatabase, boolean cleanupOccurred,
 			List<PartialFileHistory> localFileHistoriesWithLastVersion) throws Exception {
+		this.assembler = new Assembler(config, localDatabase, winnersDatabase);
 		
 		List<FileSystemAction> fileSystemActions = new ArrayList<FileSystemAction>();
 		
@@ -230,7 +233,7 @@ public class FileSystemActionReconciliator {
 			logger.log(Level.INFO, "     -> (1) Equals: Nothing to do, winning version equals winning file: "+winningLastVersion+" AND "+winningLastFile);	
 		}
 		else if (winningFileToVersionComparison.getFileChanges().contains(FileChange.DELETED)) {					
-			FileSystemAction action = new NewFileSystemAction(config, winningLastVersion, winnersDatabase);
+			FileSystemAction action = new NewFileSystemAction(config, winnersDatabase, assembler, winningLastVersion);
 			outFileSystemActions.add(action);
 			
 			logger.log(Level.INFO, "     -> (2) Deleted: Local file does NOT exist, but it should, winning version not known: "+winningLastVersion+" AND "+winningLastFile);
@@ -272,7 +275,7 @@ public class FileSystemActionReconciliator {
 			throw new Exception("What happend here?");
 		}
 		else { // Content changed
-			FileSystemAction action = new NewFileSystemAction(config, winningLastVersion, winnersDatabase);
+			FileSystemAction action = new NewFileSystemAction(config, winnersDatabase, assembler, winningLastVersion);
 			outFileSystemActions.add(action);
 
 			logger.log(Level.INFO, "     -> (7) Content changed: Winning file differs from winning version: "+winningLastVersion+" AND "+winningLastFile);
@@ -294,7 +297,7 @@ public class FileSystemActionReconciliator {
 			logger.log(Level.INFO, "     -> (8) Equals: Nothing to do, local file equals local version equals winning version: local file = "+localLastFile+", local version = "+localLastVersion+", winning version = "+winningLastVersion);
 		}
 		else if (winningVersionToLocalVersionComparison.getFileChanges().contains(FileChange.DELETED)) {
-			FileSystemAction action = new ChangeFileSystemAction(config, localLastVersion, winningLastVersion, winnersDatabase);
+			FileSystemAction action = new ChangeFileSystemAction(config, winnersDatabase, assembler, localLastVersion, winningLastVersion);
 			fileSystemActions.add(action);
 
 			logger.log(Level.INFO, "     -> (9) Content changed: Local file does not exist, but it should: local file = "+localLastFile+", local version = "+localLastVersion+", winning version = "+winningLastVersion);
@@ -333,7 +336,7 @@ public class FileSystemActionReconciliator {
 			changeSet.getChangedFiles().add(winningLastVersion.getPath());
 		}
 		else { // Content changed
-			FileSystemAction action = new ChangeFileSystemAction(config, localLastVersion, winningLastVersion, winnersDatabase);
+			FileSystemAction action = new ChangeFileSystemAction(config, winnersDatabase, assembler, localLastVersion, winningLastVersion);
 			fileSystemActions.add(action);
 
 			logger.log(Level.INFO, "     -> (13) Content changed: Local file differs from winning version: local file = "+localLastFile+", local version = "+localLastVersion+", winning version = "+winningLastVersion);
@@ -351,7 +354,7 @@ public class FileSystemActionReconciliator {
 			boolean winningLastVersionDeleted = winningLastVersion.getStatus() == FileStatus.DELETED;
 			
 			if (!winningLastVersionDeleted) {
-				FileSystemAction action = new ChangeFileSystemAction(config, localLastVersion, winningLastVersion, winnersDatabase);
+				FileSystemAction action = new ChangeFileSystemAction(config, winnersDatabase, assembler, localLastVersion, winningLastVersion);
 				fileSystemActions.add(action);
 		
 				logger.log(Level.INFO, "     -> (14) Content changed: Local file does NOT exist, and winning version changed: local file = "+localLastFile+", local version = "+localLastVersion+", winning version = "+winningLastVersion);
@@ -364,7 +367,7 @@ public class FileSystemActionReconciliator {
 			}
 		}
 		else {
-			FileSystemAction action = new ChangeFileSystemAction(config, localLastVersion, winningLastVersion, winnersDatabase);
+			FileSystemAction action = new ChangeFileSystemAction(config, winnersDatabase, assembler, winningLastVersion, localLastVersion);
 			fileSystemActions.add(action);
 	
 			logger.log(Level.INFO, "     -> (16) Content changed: Local file differs from last version: local file = "+localLastFile+", local version = "+localLastVersion+", winning version = "+winningLastVersion);
diff --git a/syncany-lib/src/main/java/org/syncany/operations/down/actions/ChangeFileSystemAction.java b/syncany-lib/src/main/java/org/syncany/operations/down/actions/ChangeFileSystemAction.java
index f367b73..1acb363 100644
--- a/syncany-lib/src/main/java/org/syncany/operations/down/actions/ChangeFileSystemAction.java
+++ b/syncany-lib/src/main/java/org/syncany/operations/down/actions/ChangeFileSystemAction.java
@@ -23,10 +23,12 @@ import org.syncany.config.Config;
 import org.syncany.database.FileVersion;
 import org.syncany.database.FileVersion.FileStatus;
 import org.syncany.database.MemoryDatabase;
+import org.syncany.operations.Assembler;
 
 public class ChangeFileSystemAction extends FileCreatingFileSystemAction {
-	public ChangeFileSystemAction(Config config, FileVersion fromFileVersion, FileVersion toFileVersion, MemoryDatabase winningDatabase) {
-		super(config, winningDatabase, fromFileVersion, toFileVersion);
+	public ChangeFileSystemAction(Config config, MemoryDatabase winningDatabase, Assembler assembler, FileVersion fromFileVersion,
+			FileVersion toFileVersion) {
+		super(config, winningDatabase, assembler, fromFileVersion, toFileVersion);
 	}
 	
 	@Override
diff --git a/syncany-lib/src/main/java/org/syncany/operations/down/actions/FileCreatingFileSystemAction.java b/syncany-lib/src/main/java/org/syncany/operations/down/actions/FileCreatingFileSystemAction.java
index 903ec0f..fddd5f9 100644
--- a/syncany-lib/src/main/java/org/syncany/operations/down/actions/FileCreatingFileSystemAction.java
+++ b/syncany-lib/src/main/java/org/syncany/operations/down/actions/FileCreatingFileSystemAction.java
@@ -24,13 +24,15 @@ import org.syncany.config.Config;
 import org.syncany.database.FileVersion;
 import org.syncany.database.FileVersion.FileType;
 import org.syncany.database.MemoryDatabase;
-import org.syncany.database.SqlDatabase;
 import org.syncany.operations.Assembler;
 import org.syncany.util.NormalizedPath;
 
 public abstract class FileCreatingFileSystemAction extends FileSystemAction {
-	public FileCreatingFileSystemAction(Config config, MemoryDatabase winningDatabase, FileVersion file1, FileVersion file2) {
-		super(config, winningDatabase, file1, file2);				
+	protected Assembler assembler;
+
+	public FileCreatingFileSystemAction(Config config, MemoryDatabase winningDatabase, Assembler assembler, FileVersion file1, FileVersion file2) {
+		super(config, winningDatabase, file1, file2);
+		this.assembler = assembler;
 	}
 
 	protected void createFileFolderOrSymlink(FileVersion reconstructedFileVersion) throws Exception {
@@ -76,9 +78,6 @@ public abstract class FileCreatingFileSystemAction extends FileSystemAction {
 	}
 	
 	protected File assembleFileToCache(FileVersion reconstructedFileVersion) throws Exception {
-		SqlDatabase localDatabase = new SqlDatabase(config);
-		Assembler assembler = new Assembler(config, localDatabase, winningDatabase);
-
 		File reconstructedFileInCache = assembler.assembleToCache(reconstructedFileVersion);
 		 
 		setFileAttributes(reconstructedFileVersion, reconstructedFileInCache);
diff --git a/syncany-lib/src/main/java/org/syncany/operations/down/actions/NewFileSystemAction.java b/syncany-lib/src/main/java/org/syncany/operations/down/actions/NewFileSystemAction.java
index 0f9f3d3..ad5fd5e 100644
--- a/syncany-lib/src/main/java/org/syncany/operations/down/actions/NewFileSystemAction.java
+++ b/syncany-lib/src/main/java/org/syncany/operations/down/actions/NewFileSystemAction.java
@@ -20,11 +20,12 @@ package org.syncany.operations.down.actions;
 import org.syncany.config.Config;
 import org.syncany.database.FileVersion;
 import org.syncany.database.MemoryDatabase;
+import org.syncany.operations.Assembler;
 
 public class NewFileSystemAction extends FileCreatingFileSystemAction {
 
-	public NewFileSystemAction(Config config, FileVersion newFileVersion, MemoryDatabase winningDatabase) {
-		super(config, winningDatabase, null, newFileVersion);
+	public NewFileSystemAction(Config config, MemoryDatabase winningDatabase, Assembler assembler, FileVersion newFileVersion) {
+		super(config, winningDatabase, assembler, null, newFileVersion);
 	}
 	
 	@Override
diff --git a/syncany-lib/src/main/java/org/syncany/operations/init/ConnectOperation.java b/syncany-lib/src/main/java/org/syncany/operations/init/ConnectOperation.java
index 45f72d6..2d1de3b 100644
--- a/syncany-lib/src/main/java/org/syncany/operations/init/ConnectOperation.java
+++ b/syncany-lib/src/main/java/org/syncany/operations/init/ConnectOperation.java
@@ -315,7 +315,7 @@ public class ConnectOperation extends AbstractInitOperation {
 				throw new RuntimeException("Repository file is encrypted, but password cannot be queried (no listener).");
 			}
 
-			return listener.onUserPassword(null, "Password: ");
+			return listener.onUserPassword(null, "Master Password: ");
 		}
 		else {
 			return options.getPassword();
diff --git a/syncany-lib/src/main/java/org/syncany/operations/restore/RestoreFileSystemAction.java b/syncany-lib/src/main/java/org/syncany/operations/restore/RestoreFileSystemAction.java
index 0cffdc7..61257c0 100644
--- a/syncany-lib/src/main/java/org/syncany/operations/restore/RestoreFileSystemAction.java
+++ b/syncany-lib/src/main/java/org/syncany/operations/restore/RestoreFileSystemAction.java
@@ -25,14 +25,15 @@ import org.syncany.database.FileVersion;
 import org.syncany.database.FileVersion.FileStatus;
 import org.syncany.database.FileVersion.FileType;
 import org.syncany.database.MemoryDatabase;
+import org.syncany.operations.Assembler;
 import org.syncany.operations.down.actions.FileCreatingFileSystemAction;
 import org.syncany.util.NormalizedPath;
 
 public class RestoreFileSystemAction extends FileCreatingFileSystemAction {
 	private String relativeTargetPath;
 	
-	public RestoreFileSystemAction(Config config, FileVersion fileVersion, String relativeTargetPath) {
-		super(config, new MemoryDatabase(), null, fileVersion);
+	public RestoreFileSystemAction(Config config, Assembler assembler, FileVersion fileVersion, String relativeTargetPath) {
+		super(config, new MemoryDatabase(), assembler, null, fileVersion);
 		this.relativeTargetPath = relativeTargetPath;
 	}
 
diff --git a/syncany-lib/src/main/java/org/syncany/operations/restore/RestoreOperation.java b/syncany-lib/src/main/java/org/syncany/operations/restore/RestoreOperation.java
index 9536b5c..7d2f505 100644
--- a/syncany-lib/src/main/java/org/syncany/operations/restore/RestoreOperation.java
+++ b/syncany-lib/src/main/java/org/syncany/operations/restore/RestoreOperation.java
@@ -32,6 +32,7 @@ import org.syncany.database.MultiChunkEntry.MultiChunkId;
 import org.syncany.database.PartialFileHistory.FileHistoryId;
 import org.syncany.database.SqlDatabase;
 import org.syncany.operations.AbstractTransferOperation;
+import org.syncany.operations.Assembler;
 import org.syncany.operations.Downloader;
 import org.syncany.operations.restore.RestoreOperationResult.RestoreResultCode;
 import org.syncany.plugins.transfer.StorageException;
@@ -45,6 +46,8 @@ public class RestoreOperation extends AbstractTransferOperation {
 	private SqlDatabase localDatabase;
 	private Downloader downloader;
 
+	private Assembler assembler;
+
 	public RestoreOperation(Config config) {
 		this(config, new RestoreOperationOptions());
 	}
@@ -55,6 +58,7 @@ public class RestoreOperation extends AbstractTransferOperation {
 		this.options = options;
 		this.localDatabase = new SqlDatabase(config);
 		this.downloader = new Downloader(config, transferManager);
+		this.assembler = new Assembler(config, localDatabase, null);
 	}
 
 	@Override
@@ -88,7 +92,7 @@ public class RestoreOperation extends AbstractTransferOperation {
 		// Restore file
 		logger.log(Level.INFO, "- Restoring: " + restoreFileVersion);
 
-		RestoreFileSystemAction restoreAction = new RestoreFileSystemAction(config, restoreFileVersion, options.getRelativeTargetPath());
+		RestoreFileSystemAction restoreAction = new RestoreFileSystemAction(config, assembler, restoreFileVersion, options.getRelativeTargetPath());
 		RestoreFileSystemActionResult restoreResult = restoreAction.execute();
 
 		return new RestoreOperationResult(RestoreResultCode.ACK, restoreResult.getTargetFile());
diff --git a/syncany-lib/src/main/java/org/syncany/operations/up/UpOperation.java b/syncany-lib/src/main/java/org/syncany/operations/up/UpOperation.java
index 9247ce7..a8147fd 100644
--- a/syncany-lib/src/main/java/org/syncany/operations/up/UpOperation.java
+++ b/syncany-lib/src/main/java/org/syncany/operations/up/UpOperation.java
@@ -106,6 +106,11 @@ public class UpOperation extends AbstractTransferOperation {
 	private UpOperationResult result;
 
 	private SqlDatabase localDatabase;
+	
+	private boolean resuming;
+	private TransactionRemoteFile transactionRemoteFileToResume;
+	private Collection<RemoteTransaction> remoteTransactionsToResume;
+	private BlockingQueue<DatabaseVersion> databaseVersionQueue;
 
 	public UpOperation(Config config) {
 		this(config, new UpOperationOptions());
@@ -117,6 +122,11 @@ public class UpOperation extends AbstractTransferOperation {
 		this.options = options;
 		this.result = new UpOperationResult();
 		this.localDatabase = new SqlDatabase(config);
+		
+		this.resuming = false;
+		this.transactionRemoteFileToResume = null;
+		this.remoteTransactionsToResume = null;
+		this.databaseVersionQueue = new LinkedBlockingQueue<>();
 	}
 
 	@Override
@@ -135,82 +145,28 @@ public class UpOperation extends AbstractTransferOperation {
 		// Upload action file (lock for cleanup)
 		startOperation();
 
-		TransactionRemoteFile transactionRemoteFileToResume = null;
-		Collection<RemoteTransaction> remoteTransactionsToResume = null;
-
-		BlockingQueue<DatabaseVersion> databaseVersionQueue = new LinkedBlockingQueue<>();
-		boolean resuming = false;
-
-		if (options.isResume()) {
-			Collection<Long> versionsToResume = transferManager.loadPendingTransactionList();
-			if (versionsToResume != null && versionsToResume.size() != 0) {
-				logger.log(Level.INFO, "Found local transaction to resume.");
-				logger.log(Level.INFO, "Attempting to find transactionRemoteFile");
-
-				remoteTransactionsToResume = attemptResumeTransactions(versionsToResume);
-				Collection<DatabaseVersion> remoteDatabaseVersionsToResume = attemptResumeDatabaseVersions(versionsToResume);
-
-				if (remoteDatabaseVersionsToResume != null && remoteTransactionsToResume != null &&
-						remoteDatabaseVersionsToResume.size() == remoteTransactionsToResume.size()) {
-					databaseVersionQueue.addAll(remoteDatabaseVersionsToResume);
-					resuming = true;
-				}
-				// Add stopping marker
-				databaseVersionQueue.add(new DatabaseVersion());
-
-				try {
-					transactionRemoteFileToResume = attemptResumeTransactionRemoteFile();
-				}
-				catch (BlockingTransfersException e) {
-					stopBecauseOfBlockingTransactions();
-					return result;
-				}
-			}
-			else {
-				transferManager.clearResumableTransactions();
+		try {
+			if (options.isResume()) {
+				prepareResume();			
 			}
-		}
 
-		if (!resuming) {
-			// Get a list of files that have been updated
-			ChangeSet localChanges = result.getStatusResult().getChangeSet();
-			List<File> locallyUpdatedFiles = extractLocallyUpdatedFiles(localChanges);
-			List<File> locallyDeletedFiles = extractLocallyDeletedFiles(localChanges);
-			// Iterate over the changes, deduplicate, and feed DatabaseVersions into an iterator
-			Deduper deduper = new Deduper(config.getChunker(), config.getMultiChunker(), config.getTransformer(), options.getTransactionSizeLimit(),
-					options.getTransactionFileLimit());
-			
-			AsyncIndexer asyncIndexer = new AsyncIndexer(config, deduper, locallyUpdatedFiles, locallyDeletedFiles, databaseVersionQueue);
-			new Thread(asyncIndexer).start();
-		}
-
-		// If we are not resuming from a remote transaction, we need to clean transactions.
-		if (transactionRemoteFileToResume == null) {
-			try {
-				transferManager.cleanTransactions();
-			}
-			catch (BlockingTransfersException e) {
-				stopBecauseOfBlockingTransactions();
-				return result;
+			if (!resuming) {
+				startIndexerThread(databaseVersionQueue);			
 			}
-		}
 
-		int numberOfPerformedTransactions = 0;
-		if (resuming) {
-			numberOfPerformedTransactions = executeTransactions(databaseVersionQueue, remoteTransactionsToResume.iterator(), transactionRemoteFileToResume);
+			// If we are not resuming from a remote transaction, we need to clean transactions.
+			if (transactionRemoteFileToResume == null) {
+				transferManager.cleanTransactions();
+			}			
 		}
-		else {
-			numberOfPerformedTransactions = executeTransactions(databaseVersionQueue);
+		catch (BlockingTransfersException e) {
+			stopBecauseOfBlockingTransactions();
+			return result;
 		}
 		
-		// Check if anything has happened.
-		if (numberOfPerformedTransactions == 0) {
-			logger.log(Level.INFO, "Local database is up-to-date. NOTHING TO DO!");
-			result.setResultCode(UpResultCode.OK_NO_CHANGES);
-		} else {
-			logger.log(Level.INFO, "Sync up done.");
-			result.setResultCode(UpResultCode.OK_CHANGES_UPLOADED);
-		}
+		// Go wild
+		int numberOfPerformedTransactions = executeTransactions();
+		updateResult(numberOfPerformedTransactions);		
 
 		// Close database connection
 		localDatabase.finalize();
@@ -218,17 +174,62 @@ public class UpOperation extends AbstractTransferOperation {
 		// Finish 'up' before 'cleanup' starts
 		finishOperation();
 		fireEndEvent();
+		
 		return result;
 	}
 
-	/**
-	 *	Transfers the given {@link DatabaseVersion} objects to the remote.
-	 *	Each {@link DatabaseVersion} will be transferred in its own {@link RemoteTransaction} object.
-	 *
-	 *	@param databaseVersions The {@link DatabaseVersion} objects to send to the remote.
-	 */
-	private int executeTransactions(BlockingQueue<DatabaseVersion> databaseVersionQueue) throws Exception {
-		return executeTransactions(databaseVersionQueue, null, null);
+	private void updateResult(int numberOfPerformedTransactions) {
+		if (numberOfPerformedTransactions == 0) {
+			logger.log(Level.INFO, "Local database is up-to-date. NOTHING TO DO!");
+			result.setResultCode(UpResultCode.OK_NO_CHANGES);
+		}
+		else {
+			logger.log(Level.INFO, "Sync up done.");
+			result.setResultCode(UpResultCode.OK_CHANGES_UPLOADED);
+		}
+	}
+
+	private void startIndexerThread(BlockingQueue<DatabaseVersion> databaseVersionQueue) {
+		// Get a list of files that have been updated
+		ChangeSet localChanges = result.getStatusResult().getChangeSet();
+		List<File> locallyUpdatedFiles = extractLocallyUpdatedFiles(localChanges);
+		List<File> locallyDeletedFiles = extractLocallyDeletedFiles(localChanges);
+		
+		// Iterate over the changes, deduplicate, and feed DatabaseVersions into an iterator
+		Deduper deduper = new Deduper(config.getChunker(), config.getMultiChunker(), config.getTransformer(), options.getTransactionSizeLimit(),
+				options.getTransactionFileLimit());
+		
+		AsyncIndexer asyncIndexer = new AsyncIndexer(config, deduper, locallyUpdatedFiles, locallyDeletedFiles, databaseVersionQueue);
+		new Thread(asyncIndexer, "AsyncI/" + config.getLocalDir().getName()).start();
+	}
+
+	private void prepareResume() throws Exception {	
+		Collection<Long> versionsToResume = transferManager.loadPendingTransactionList();
+		boolean hasVersionsToResume = versionsToResume != null && versionsToResume.size() > 0;
+
+		if (hasVersionsToResume) {
+			logger.log(Level.INFO, "Found local transaction to resume.");
+			logger.log(Level.INFO, "Attempting to find transactionRemoteFile");
+
+			remoteTransactionsToResume = attemptResumeTransactions(versionsToResume);
+			Collection<DatabaseVersion> remoteDatabaseVersionsToResume = attemptResumeDatabaseVersions(versionsToResume);
+
+			resuming = remoteDatabaseVersionsToResume != null && remoteTransactionsToResume != null &&
+					remoteDatabaseVersionsToResume.size() == remoteTransactionsToResume.size();
+			
+			if (resuming) {
+				databaseVersionQueue.addAll(remoteDatabaseVersionsToResume);				
+				databaseVersionQueue.add(new DatabaseVersion()); // Empty database version is the stopping marker			
+				
+				transactionRemoteFileToResume = attemptResumeTransactionRemoteFile();			
+			} 
+			else {
+				transferManager.clearResumableTransactions();
+			}
+		}
+		else {
+			transferManager.clearResumableTransactions();
+		}
 	}
 
 	/**
@@ -246,16 +247,9 @@ public class UpOperation extends AbstractTransferOperation {
 	 *	@param remoteTransactionsToResume {@link RemoteTransaction} objects that correspond to the given {@link DatabaseVersion} objects.
 	 *	@param transactionRemoteFileToResume The file on the remote that was used for the specific transaction that was interrupted.
 	 */
-	private int executeTransactions(BlockingQueue<DatabaseVersion> databaseVersionQueue, Iterator<RemoteTransaction> remoteTransactionsToResume,
-			TransactionRemoteFile transactionRemoteFileToResume)
-			throws Exception {
+	private int executeTransactions() throws Exception {
+		Iterator<RemoteTransaction> remoteTransactionsToResumeIterator = (resuming) ? remoteTransactionsToResume.iterator() : null;
 		
-		boolean resuming = true;
-		
-		if (remoteTransactionsToResume == null) {
-			resuming = false;
-		}
-
 		// At this point, if a failure occurs from which we can resume, new transaction files will be written
 		// Delete any old transaction files
 		transferManager.clearPendingTransactions();
@@ -267,14 +261,17 @@ public class UpOperation extends AbstractTransferOperation {
 		
 		DatabaseVersion databaseVersion = databaseVersionQueue.take();
 		boolean noDatabaseVersions = databaseVersion.isEmpty();
+		
 		// Add dirty data to first database
 		addDirtyData(databaseVersion);
 
 		//
 		while (!databaseVersion.isEmpty()) {
 			RemoteTransaction remoteTransaction = null;
+			
 			if (!resuming) {
 				VectorClock newVectorClock = findNewVectorClock();
+				
 				databaseVersion.setVectorClock(newVectorClock);
 				databaseVersion.setTimestamp(new Date());
 				databaseVersion.setClient(config.getMachineName());
@@ -283,11 +280,12 @@ public class UpOperation extends AbstractTransferOperation {
 
 				// Add multichunks to transaction
 				logger.log(Level.INFO, "Uploading new multichunks ...");
+				
 				// This call adds newly changed chunks to a "RemoteTransaction", so they can be uploaded later.
 				addMultiChunksToTransaction(remoteTransaction, databaseVersion.getMultiChunks());
 			}
 			else {
-				remoteTransaction = remoteTransactionsToResume.next();
+				remoteTransaction = remoteTransactionsToResumeIterator.next();
 			}
 
 			logger.log(Level.INFO, "Uploading database: " + databaseVersion);
@@ -395,6 +393,7 @@ public class UpOperation extends AbstractTransferOperation {
 		else {
 			transactionRemoteFile = transactions.get(0);
 		}
+		
 		return transactionRemoteFile;
 	}
 
@@ -674,6 +673,7 @@ public class UpOperation extends AbstractTransferOperation {
 	private void addLocalDatabaseToTransaction(RemoteTransaction remoteTransaction, File localDatabaseFile, DatabaseRemoteFile remoteDatabaseFile)
 			throws InterruptedException,
 			StorageException {
+		
 		logger.log(Level.INFO, "- Uploading " + localDatabaseFile + " to " + remoteDatabaseFile + " ...");
 		remoteTransaction.upload(localDatabaseFile, remoteDatabaseFile);
 	}
@@ -727,56 +727,70 @@ public class UpOperation extends AbstractTransferOperation {
 		return newVectorClock;
 	}
 
-	private Collection<RemoteTransaction> attemptResumeTransactions(Collection<Long> versions) throws Exception {
-		Collection<RemoteTransaction> remoteTransactions = new ArrayList<>();
-		for (Long version : versions) {
-			File transactionFile = config.getTransactionFile(version);
+	private Collection<RemoteTransaction> attemptResumeTransactions(Collection<Long> versions) {
+		try {
+			Collection<RemoteTransaction> remoteTransactions = new ArrayList<>();
 
-			// If a single transaction file is missing, we should restart
-			if (!transactionFile.exists()) {
-				return null;
-			}
+			for (Long version : versions) {
+				File transactionFile = config.getTransactionFile(version);
+
+				// If a single transaction file is missing, we should restart
+				if (!transactionFile.exists()) {
+					return null;
+				}
 
-			TransactionTO transactionTO = TransactionTO.load(null, transactionFile);
+				TransactionTO transactionTO = TransactionTO.load(null, transactionFile);
 
-			// Verify if all files needed are in cache.
-			for (ActionTO action : transactionTO.getActions()) {
-				if (action.getType() == ActionType.UPLOAD) {
-					if (action.getStatus() == ActionStatus.UNSTARTED) {
-						if (!action.getLocalTempLocation().exists()) {
-							// Unstarted upload has no cached local copy, abort
-							return null;
+				// Verify if all files needed are in cache.
+				for (ActionTO action : transactionTO.getActions()) {
+					if (action.getType() == ActionType.UPLOAD) {
+						if (action.getStatus() == ActionStatus.UNSTARTED) {
+							if (!action.getLocalTempLocation().exists()) {
+								// Unstarted upload has no cached local copy, abort
+								return null;
+							}
 						}
 					}
 				}
-			}
 
-			remoteTransactions.add(new RemoteTransaction(config, transferManager, transactionTO));
+				remoteTransactions.add(new RemoteTransaction(config, transferManager, transactionTO));
+			}
+			
+			return remoteTransactions;
+		} catch (Exception e) {
+			logger.log(Level.WARNING, "Invalid transaction file. Cannot resume!");
+			return null;
 		}
-		return remoteTransactions;
 	}
 
 	private Collection<DatabaseVersion> attemptResumeDatabaseVersions(Collection<Long> versions) throws Exception {
-		Collection<DatabaseVersion> databaseVersions = new ArrayList<>();
-		for (Long version : versions) {
-			File databaseFile = config.getTransactionDatabaseFile(version);
+		try {
+			Collection<DatabaseVersion> databaseVersions = new ArrayList<>();
+			
+			for (Long version : versions) {
+				File databaseFile = config.getTransactionDatabaseFile(version);
 
-			// If a single database file is missing, we should restart
-			if (!databaseFile.exists()) {
-				return null;
-			}
+				// If a single database file is missing, we should restart
+				if (!databaseFile.exists()) {
+					return null;
+				}
 
-			DatabaseXmlSerializer databaseSerializer = new DatabaseXmlSerializer();
-			MemoryDatabase memoryDatabase = new MemoryDatabase();
-			databaseSerializer.load(memoryDatabase, databaseFile, null, null, DatabaseReadType.FULL);
+				DatabaseXmlSerializer databaseSerializer = new DatabaseXmlSerializer();
+				MemoryDatabase memoryDatabase = new MemoryDatabase();
+				databaseSerializer.load(memoryDatabase, databaseFile, null, null, DatabaseReadType.FULL);
 
-			if (memoryDatabase.getDatabaseVersions().size() == 0) {
-				return null;
-			}
+				if (memoryDatabase.getDatabaseVersions().size() == 0) {
+					return null;
+				}
 
-			databaseVersions.add(memoryDatabase.getLastDatabaseVersion());
+				databaseVersions.add(memoryDatabase.getLastDatabaseVersion());
+			}
+			
+			return databaseVersions;			
+		} catch (Exception e) {
+			logger.log(Level.WARNING, "Cannot load database versions from 'state'. Cannot resume.");
+			return null;
 		}
-		return databaseVersions;
 	}
 
 	/**
diff --git a/syncany-lib/src/main/java/org/syncany/plugins/local/LocalTransferManager.java b/syncany-lib/src/main/java/org/syncany/plugins/local/LocalTransferManager.java
index 1422059..e7ee603 100644
--- a/syncany-lib/src/main/java/org/syncany/plugins/local/LocalTransferManager.java
+++ b/syncany-lib/src/main/java/org/syncany/plugins/local/LocalTransferManager.java
@@ -229,7 +229,7 @@ public class LocalTransferManager extends AbstractTransferManager {
 				}
 				catch (StorageException e) {
 					logger.log(Level.INFO, "Cannot create instance of " + remoteFileClass.getSimpleName() + " for file " + path
-									+ "; maybe invalid file name pattern. Ignoring file.", e);
+									+ "; maybe invalid file name pattern. Ignoring file.");
 				}
 			}
 		}
diff --git a/syncany-lib/src/main/java/org/syncany/plugins/transfer/features/TransactionAwareFeatureTransferManager.java b/syncany-lib/src/main/java/org/syncany/plugins/transfer/features/TransactionAwareFeatureTransferManager.java
index bc5c582..b5be175 100644
--- a/syncany-lib/src/main/java/org/syncany/plugins/transfer/features/TransactionAwareFeatureTransferManager.java
+++ b/syncany-lib/src/main/java/org/syncany/plugins/transfer/features/TransactionAwareFeatureTransferManager.java
@@ -279,16 +279,25 @@ public class TransactionAwareFeatureTransferManager implements FeatureTransferMa
 	 */
 	public Collection<Long> loadPendingTransactionList() throws IOException {
 		Objects.requireNonNull(config, "Cannot read pending transaction list if config is null.");
+		
 		Collection<Long> databaseVersionNumbers = new ArrayList<>();
 		File transactionListFile = config.getTransactionListFile();
+		
 		if (!transactionListFile.exists()) {
 			return Collections.emptyList();
 		}
 
 		Collection<String> transactionLines = Files.readAllLines(transactionListFile.toPath(), Charset.forName("UTF-8"));
+		
 		for (String transactionLine : transactionLines) {
-			databaseVersionNumbers.add(Long.parseLong(transactionLine));
+			try {
+				databaseVersionNumbers.add(Long.parseLong(transactionLine));	
+			} catch (NumberFormatException e) {
+				logger.log(Level.WARNING, "Cannot parse line in transaction list: " + transactionLine + ". Cannot resume.");
+				return Collections.emptyList(); 
+			}			
 		}
+		
 		return databaseVersionNumbers;
 	}
 
diff --git a/syncany-lib/src/test/integration/java/org/syncany/tests/ScenarioTestSuite.java b/syncany-lib/src/test/integration/java/org/syncany/tests/ScenarioTestSuite.java
index f24be0d..64d7d04 100644
--- a/syncany-lib/src/test/integration/java/org/syncany/tests/ScenarioTestSuite.java
+++ b/syncany-lib/src/test/integration/java/org/syncany/tests/ScenarioTestSuite.java
@@ -48,6 +48,8 @@ import org.syncany.tests.integration.scenarios.Issue288ScenarioTest;
 import org.syncany.tests.integration.scenarios.Issue303ScenarioTest;
 import org.syncany.tests.integration.scenarios.Issue316ScenarioTest;
 import org.syncany.tests.integration.scenarios.Issue374Pre1965DateScenarioTest;
+import org.syncany.tests.integration.scenarios.Issue429ScenarioTest;
+import org.syncany.tests.integration.scenarios.Issue520NoResumeOnCorruptXmlScenarioTest;
 import org.syncany.tests.integration.scenarios.ManyRenamesScenarioTest;
 import org.syncany.tests.integration.scenarios.ManySyncUpsAndDatabaseFileCleanupScenarioTest;
 import org.syncany.tests.integration.scenarios.ManySyncUpsAndOtherClientSyncDownScenarioTest;
@@ -94,6 +96,8 @@ import org.syncany.tests.integration.scenarios.SymlinkSyncScenarioTest;
 		Issue303ScenarioTest.class,
 		Issue316ScenarioTest.class,
 		Issue374Pre1965DateScenarioTest.class,
+		Issue429ScenarioTest.class,
+		Issue520NoResumeOnCorruptXmlScenarioTest.class,
 		ManyRenamesScenarioTest.class,
 		ManySyncUpsAndDatabaseFileCleanupScenarioTest.class,
 		ManySyncUpsLargeFileScenarioTest.class,
diff --git a/syncany-lib/src/test/integration/java/org/syncany/tests/integration/operations/FileSystemActionComparatorTest.java b/syncany-lib/src/test/integration/java/org/syncany/tests/integration/operations/FileSystemActionComparatorTest.java
index 1648abc..7267076 100644
--- a/syncany-lib/src/test/integration/java/org/syncany/tests/integration/operations/FileSystemActionComparatorTest.java
+++ b/syncany-lib/src/test/integration/java/org/syncany/tests/integration/operations/FileSystemActionComparatorTest.java
@@ -83,7 +83,7 @@ public class FileSystemActionComparatorTest {
 
 	private NewFileSystemAction createNewFileSystemAction(String path, FileType type) throws Exception {
 		FileVersion firstFileVersion = createFileVersion(path, type);
-		return new NewFileSystemAction(createDummyConfig(), firstFileVersion, null);
+		return new NewFileSystemAction(createDummyConfig(), null, null, firstFileVersion);
 	}
 	
 	private RenameFileSystemAction createRenameFileSystemAction(String fromPath, String toPath, FileType type) throws Exception {
diff --git a/syncany-lib/src/test/integration/java/org/syncany/tests/integration/scenarios/Issue520NoResumeOnCorruptXmlScenarioTest.java b/syncany-lib/src/test/integration/java/org/syncany/tests/integration/scenarios/Issue520NoResumeOnCorruptXmlScenarioTest.java
new file mode 100644
index 0000000..ed1737d
--- /dev/null
+++ b/syncany-lib/src/test/integration/java/org/syncany/tests/integration/scenarios/Issue520NoResumeOnCorruptXmlScenarioTest.java
@@ -0,0 +1,77 @@
+/*
+ * Syncany, www.syncany.org
+ * Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel at 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.tests.integration.scenarios;
+
+import java.io.File;
+
+import org.junit.Test;
+import org.syncany.plugins.local.LocalTransferSettings;
+import org.syncany.tests.unit.util.TestFileUtil;
+import org.syncany.tests.util.TestAssertUtil;
+import org.syncany.tests.util.TestClient;
+import org.syncany.tests.util.TestConfigUtil;
+
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class Issue520NoResumeOnCorruptXmlScenarioTest {
+	@Test
+	public void testCorruptTransactionListFile() throws Exception {
+		// Setup 
+		LocalTransferSettings testConnection = (LocalTransferSettings) TestConfigUtil.createTestLocalConnection();
+
+		TestClient clientA = new TestClient("A", testConnection);
+		
+		clientA.createNewFile("file1.txt", 1024);
+		clientA.upWithForceChecksum();
+		
+		TestFileUtil.createFileWithContent(new File(clientA.getConfig().getAppDir(), "/state/transaction-list.txt"), "INVALID");
+		
+		clientA.createNewFile("file2.txt", 1024);
+		clientA.upWithForceChecksum();  // This did FAIL due to an XML parsing exception
+		
+		assertEquals("There should be exactly two database files", 2, new File(testConnection.getPath() + "/databases").listFiles().length);
+		assertEquals("There should be exactly two multichunks", 2, new File(testConnection.getPath() + "/multichunks").listFiles().length);
+		
+		// Tear down
+		clientA.deleteTestData();
+	}	
+
+	@Test
+	public void testCorruptTransactionFile() throws Exception {
+		// Setup 
+		LocalTransferSettings testConnection = (LocalTransferSettings) TestConfigUtil.createTestLocalConnection();
+
+		TestClient clientA = new TestClient("A", testConnection);
+		
+		clientA.createNewFile("file1.txt", 1024);
+		clientA.upWithForceChecksum();
+		
+		TestFileUtil.createFileWithContent(new File(clientA.getConfig().getAppDir(), "/state/transaction-list.txt"), "0000000001");
+		TestFileUtil.createFileWithContent(new File(clientA.getConfig().getAppDir(), "/state/transaction-database.0000000001.xml"), "invalid");
+		
+		clientA.createNewFile("file2.txt", 1024);
+		clientA.upWithForceChecksum();  // This did FAIL due to an XML parsing exception
+		
+		assertEquals("There should be exactly two database files", 2, new File(testConnection.getPath() + "/databases").listFiles().length);
+		assertEquals("There should be exactly two multichunks", 2, new File(testConnection.getPath() + "/multichunks").listFiles().length);
+
+		// Tear down
+		clientA.deleteTestData();
+	}	
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/syncany.git



More information about the pkg-java-commits mailing list