public DatabaseBranch findLosersPruneBranch(DatabaseBranch losersBranch, DatabaseBranch winnersBranch) { DatabaseBranch losersPruneBranch = new DatabaseBranch(); boolean pruneBranchStarted = false; for (int i = 0; i < losersBranch.size(); i++) { if (pruneBranchStarted) { losersPruneBranch.add(losersBranch.get(i)); } else if (i < winnersBranch.size() && !losersBranch.get(i).equals(winnersBranch.get(i))) { pruneBranchStarted = true; losersPruneBranch.add(losersBranch.get(i)); } } return losersPruneBranch; }
@Override public DatabaseBranch clone() { DatabaseBranch clonedBranch = new DatabaseBranch(); clonedBranch.addAll(getAll()); return clonedBranch; }
public static DatabaseBranch createBranch(String[] databaseVersionHeaderStrings) throws Exception { DatabaseBranch branch = new DatabaseBranch(); for (String databaseVersionHeaderString : databaseVersionHeaderStrings) { DatabaseVersionHeader databaseVersionHeader = createFromString(databaseVersionHeaderString); branch.add(databaseVersionHeader); } return branch; }
DatabaseBranch winnersBranch = new DatabaseBranch(); DatabaseVersionHeaderComparator databaseVersionHeaderComparator = new DatabaseVersionHeaderComparator(false); boolean emptyWinnerBranch = winnersBranch.size() == 0; boolean potentialWinnerWins = !emptyWinnerBranch && databaseVersionHeaderComparator.compare(potentialWinner, winnersBranch.getLast()) > 0; winnersBranch.add(potentialWinner); DatabaseVersionHeader winningLastDatabaseVersionHeader = winnersBranch.getLast(); DatabaseVersionHeader currentBranchLastDatabaseVersionHeader = currentBranch.getLast();
public DatabaseBranch getBranch(String client, boolean createIfNotExistant) { DatabaseBranch branch = branches.get(client); if (branch == null && createIfNotExistant) { branch = new DatabaseBranch(); branches.put(client, branch); } return branch; }
/** * This methods takes a Map containing DatabaseVersions (headers only) and loads these headers into {@link DatabaseBranches}. * In addition, the local branch is added to this. The resulting DatabaseBranches will contain all headers exactly once, * for the client that created that version. * * @param localBranch {@link DatabaseBranch} containing the locally known headers. * @param remoteDatabaseHeaders Map from {@link DatabaseRemoteFile}s (important for client names) to the {@link DatabaseVersion}s that are * contained in these files. * * @return DatabaseBranches filled with all the headers that originated from either of the parameters. */ private DatabaseBranches populateDatabaseBranches(DatabaseBranch localBranch, SortedMap<DatabaseRemoteFile, List<DatabaseVersion>> remoteDatabaseHeaders) { DatabaseBranches allBranches = new DatabaseBranches(); allBranches.put(config.getMachineName(), localBranch.clone()); for (DatabaseRemoteFile remoteDatabaseFile : remoteDatabaseHeaders.keySet()) { // Populate branches DatabaseBranch remoteClientBranch = allBranches.getBranch(remoteDatabaseFile.getClientName(), true); for (DatabaseVersion remoteDatabaseVersion : remoteDatabaseHeaders.get(remoteDatabaseFile)) { DatabaseVersionHeader header = remoteDatabaseVersion.getHeader(); remoteClientBranch.add(header); } } logger.log(Level.INFO, "Populated unknown branches: " + allBranches); return allBranches; }
public DatabaseFileReader(DatabaseXmlSerializer databaseSerializer, DatabaseBranch winnersApplyBranch, Map<DatabaseVersionHeader, File> databaseVersionLocations) { this.winnersApplyBranchList = winnersApplyBranch.getAll(); this.databaseVersionLocations = databaseVersionLocations; this.databaseSerializer = databaseSerializer; }
logger.log(Level.INFO, "- Database versions to REMOVE locally: " + localPurgeBranch); if (localPurgeBranch.size() == 0) { logger.log(Level.INFO, " + Nothing to purge locally. No conflicts. Only updates. Nice!"); for (DatabaseVersionHeader databaseVersionHeader : localPurgeBranch.getAll()) { logger.log(Level.INFO, " * MASTER->DIRTY: " + databaseVersionHeader); localDatabase.markDatabaseVersionDirty(databaseVersionHeader.getVectorClock());
logger.log(Level.INFO, "- Database versions to APPLY locally: " + winnersApplyBranch); boolean remoteChangesOccurred = winnersApplyBranch.size() > 0 || cleanupOccurred;
public DatabaseBranch getLocalDatabaseBranch() { DatabaseBranch databaseBranch = new DatabaseBranch(); databaseBranch.add(currentDatabaseVersionHeader); databaseBranch.add(currentDatabaseVersionHeader);
/** * This method uses the {@link DatabaseReconciliator} to compare the local database with the * downloaded remote databases, in order to determine a winner. The winner's database versions * will be applied locally. * * <p>For the comparison, the {@link DatabaseVersionHeader}s (mainly the {@link VectorClock}) of each * database version are compared. Using these vector clocks, the underlying algorithms determine * potential conflicts (between database versions, = simultaneous vector clocks), and resolve these * conflicts by comparing local timestamps. * * <p>The detailed algorithm is described in the {@link DatabaseReconciliator}. * * @param localBranch Local database branch (extracted from the local database) * @param allStitchedBranches The newly downloaded remote database version headers (= branches) * @return Returns the branch of the winner * @throws Exception If any kind of error occurs (...) */ private Map.Entry<String, DatabaseBranch> determineWinnerBranch(DatabaseBranches allStitchedBranches) throws Exception { logger.log(Level.INFO, "Determine winner using database reconciliator ..."); Entry<String, DatabaseBranch> winnersBranch = databaseReconciliator.findWinnerBranch(allStitchedBranches); if (winnersBranch != null) { return winnersBranch; } else { return new AbstractMap.SimpleEntry<String, DatabaseBranch>("", new DatabaseBranch()); } }
/** * Persists the given winners branch to the local database, i.e. for every database version * in the winners branch, all contained multichunks, chunks, etc. are added to the local SQL * database. * * <p>This method applies both regular database versions as well as purge database versions. */ private void persistDatabaseVersions(DatabaseBranch winnersApplyBranch, MemoryDatabase winnersDatabase) throws SQLException { // Add winners database to local database // Note: This must happen AFTER the file system stuff, because we compare the winners database with the local database! logger.log(Level.INFO, "- Adding database versions to SQL database ..."); for (DatabaseVersionHeader currentDatabaseVersionHeader : winnersApplyBranch.getAll()) { persistDatabaseVersion(winnersDatabase, currentDatabaseVersionHeader); } }
public DatabaseBranch findWinnersApplyBranch(DatabaseBranch losersBranch, DatabaseBranch winnersBranch) { logger.log(Level.INFO, "Finding winnersApplyBranch."); logger.log(Level.INFO, "Losers Branch: " + losersBranch); logger.log(Level.INFO, "Winners Branch: " + winnersBranch); DatabaseBranch winnersApplyBranch = new DatabaseBranch(); boolean applyBranchStarted = false; for (int i = 0; i < winnersBranch.size(); i++) { if (!applyBranchStarted) { if (i >= losersBranch.size() || !losersBranch.get(i).equals(winnersBranch.get(i))) { applyBranchStarted = true; } } if (applyBranchStarted) { winnersApplyBranch.add(winnersBranch.get(i)); } } return winnersApplyBranch; }
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"); } // We only persist the versions that we have already applied. DatabaseBranch currentApplyBranch = new DatabaseBranch(); for (DatabaseVersion databaseVersion : winnersDatabase.getDatabaseVersions()) { currentApplyBranch.add(databaseVersion.getHeader()); } persistDatabaseVersions(currentApplyBranch, winnersDatabase); localDatabase.commit(); }
localDatabase.writeCleanupTime(System.currentTimeMillis() / 1000); localBranch = new DatabaseBranch();
private List<DatabaseVersionHeader> sortBranches(DatabaseBranches allBranches) { List<DatabaseVersionHeader> databaseVersionHeaders = new ArrayList<DatabaseVersionHeader>(); for (String client : allBranches.getClients()) { databaseVersionHeaders.addAll(allBranches.getBranch(client).getAll()); } Collections.sort(databaseVersionHeaders, new DatabaseVersionHeaderComparator(true)); return databaseVersionHeaders; } }
/** * Implements the core synchronization algorithm as described {@link DatabaseReconciliator in the class description}. * * @param localMachineName Client name of the local machine (required for branch stitching) * @param localBranch Local branch, created from the local database * @param unknownRemoteBranches Newly downloaded unknown remote branches (incomplete branches; will be stitched) * @return Returns the branch of the winning client */ public Map.Entry<String, DatabaseBranch> findWinnerBranch(DatabaseBranches allBranches) throws Exception { Entry<String, DatabaseBranch> winnersNameAndBranch = findWinnersNameAndBranch(allBranches); if (winnersNameAndBranch != null) { String winnersName = winnersNameAndBranch.getKey(); DatabaseBranch winnersBranch = winnersNameAndBranch.getValue(); if (logger.isLoggable(Level.INFO)) { logger.log(Level.INFO, "- Winner is " + winnersName + " with branch: "); for (DatabaseVersionHeader databaseVersionHeader : winnersBranch.getAll()) { logger.log(Level.INFO, " + " + databaseVersionHeader); } } return winnersNameAndBranch; } else { return null; } }