/* * Syncany, www.syncany.org * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.syncany.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"); } }