001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.operations.down; 019 020import java.io.File; 021import java.io.IOException; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025 026import org.syncany.database.DatabaseVersion; 027import org.syncany.database.DatabaseVersionHeader; 028import org.syncany.database.MemoryDatabase; 029import org.syncany.database.VectorClock; 030import org.syncany.database.dao.DatabaseXmlSerializer; 031import org.syncany.database.dao.DatabaseXmlSerializer.DatabaseReadType; 032 033/** 034 * The DatabaseFileReader provides a way to read a series of database files 035 * in a memory-efficient way, by converting them to a series of MemoryDatabases, 036 * none of which are too large. 037 * 038 * @author Pim Otte 039 */ 040public class DatabaseFileReader implements Iterator<MemoryDatabase> { 041 private static final int MAX_FILES = 9999; 042 043 private DatabaseXmlSerializer databaseSerializer; 044 private List<DatabaseVersionHeader> winnersApplyBranchList; 045 private Map<DatabaseVersionHeader, File> databaseVersionLocations; 046 private int branchIndex = 0; 047 048 public DatabaseFileReader(DatabaseXmlSerializer databaseSerializer, DatabaseBranch winnersApplyBranch, 049 Map<DatabaseVersionHeader, File> databaseVersionLocations) { 050 051 this.winnersApplyBranchList = winnersApplyBranch.getAll(); 052 this.databaseVersionLocations = databaseVersionLocations; 053 this.databaseSerializer = databaseSerializer; 054 } 055 056 public boolean hasNext() { 057 return branchIndex < winnersApplyBranchList.size(); 058 } 059 060 /** 061 * Loads the winner's database branch into the memory in a {@link MemoryDatabase} object, by using 062 * the already downloaded list of remote database files. 063 * 064 * <p>Because database files can contain multiple {@link DatabaseVersion}s per client, a range for which 065 * to load the database versions must be determined. 066 * 067 * <p><b>Example 1:</b><br> 068 * <pre> 069 * db-A-0001 (A1) Already known Not loaded 070 * db-A-0005 (A2) Already known Not loaded 071 * (A3) Already known Not loaded 072 * (A4) Part of winner's branch Loaded 073 * (A5) Purge database version Ignored (only DEFAULT) 074 * db-B-0001 (A5,B1) Part of winner's branch Loaded 075 * db-A-0006 (A6,B1) Part of winner's branch Loaded 076 * </pre> 077 * 078 * <p>In example 1, only (A4)-(A5) must be loaded from db-A-0005, and not all four database versions. 079 * 080 * <p><b>Other example:</b><br> 081 * <pre> 082 * db-A-0005 (A1) Part of winner's branch Loaded 083 * db-A-0005 (A2) Part of winner's branch Loaded 084 * db-B-0001 (A2,B1) Part of winner's branch Loaded 085 * db-A-0005 (A3,B1) Part of winner's branch Loaded 086 * db-A-0005 (A4,B1) Part of winner's branch Loaded 087 * db-A-0005 (A5,B1) Purge database version Ignored (only DEFAULT) 088 * </pre> 089 * 090 * <p>In example 2, (A1)-(A5,B1) [except (A2,B1)] are contained in db-A-0005 (after merging!), so 091 * db-A-0005 must be processed twice; each time loading separate parts of the file. In this case: 092 * First load (A1)-(A2) from db-A-0005, then load (A2,B1) from db-B-0001, then load (A3,B1)-(A4,B1) 093 * from db-A-0005, and ignore (A5,B1). 094 * 095 * @return Returns a loaded memory database containing all metadata from the winner's branch 096 */ 097 @Override 098 public MemoryDatabase next() { 099 MemoryDatabase winnerBranchDatabase = new MemoryDatabase(); 100 String rangeClientName = null; 101 VectorClock rangeVersionFrom = null; 102 VectorClock rangeVersionTo = null; 103 104 while (branchIndex < winnersApplyBranchList.size() && winnerBranchDatabase.getFileHistories().size() < MAX_FILES) { 105 DatabaseVersionHeader currentDatabaseVersionHeader = winnersApplyBranchList.get(branchIndex); 106 DatabaseVersionHeader nextDatabaseVersionHeader = (branchIndex + 1 < winnersApplyBranchList.size()) ? winnersApplyBranchList 107 .get(branchIndex + 1) : null; 108 109 // First of range for this client 110 if (rangeClientName == null) { 111 rangeClientName = currentDatabaseVersionHeader.getClient(); 112 rangeVersionFrom = currentDatabaseVersionHeader.getVectorClock(); 113 rangeVersionTo = currentDatabaseVersionHeader.getVectorClock(); 114 } 115 116 // Still in range for this client 117 else { 118 rangeVersionTo = currentDatabaseVersionHeader.getVectorClock(); 119 } 120 121 // Now load this stuff from the database file (or not) 122 // - If the database file exists, load the range and reset it 123 // - If not, only force a load if this is the range end 124 125 File databaseVersionFile = databaseVersionLocations.get(currentDatabaseVersionHeader); 126 127 if (databaseVersionFile == null) { 128 throw new RuntimeException("Could not find file corresponding to " + currentDatabaseVersionHeader 129 + ", while it is in the winners branch."); 130 } 131 132 boolean lastDatabaseVersionHeader = nextDatabaseVersionHeader == null; 133 boolean nextDatabaseVersionInSameFile = lastDatabaseVersionHeader 134 || databaseVersionFile.equals(databaseVersionLocations.get(nextDatabaseVersionHeader)); 135 boolean rangeEnds = lastDatabaseVersionHeader || !nextDatabaseVersionInSameFile; 136 137 if (rangeEnds) { 138 try { 139 databaseSerializer.load(winnerBranchDatabase, databaseVersionFile, rangeVersionFrom, rangeVersionTo, DatabaseReadType.FULL); 140 } 141 catch (IOException e) { 142 throw new RuntimeException(e.getMessage(), e); 143 } 144 rangeClientName = null; 145 } 146 branchIndex++; 147 } 148 149 return winnerBranchDatabase; 150 } 151 152 @Override 153 public void remove() { 154 throw new UnsupportedOperationException("Removing a databaseversion is not supported"); 155 156 } 157 158}