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 <[email protected]> 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 <[email protected]> + * + * 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 <[email protected]> + * + * 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 _______________________________________________ pkg-java-commits mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-java-commits

