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.chunk; 019 020import java.io.File; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.util.Map; 025import java.util.logging.Level; 026import java.util.logging.Logger; 027 028import org.syncany.database.MultiChunkEntry.MultiChunkId; 029import org.syncany.util.StringUtil; 030 031/** 032 * A multichunker combines a set of {@link Chunk}s into a single file. It can be implemented 033 * by a simple container or archive format. The multichunker is used by the {@link Deduper} 034 * to write multichunks, and by other parts of the application to read multichunks and 035 * re-assemble files. 036 * 037 * <p>The class supports two modes: 038 * 039 * <ul> 040 * <li>When writing a {@link MultiChunker}, the {@link #createMultiChunk(MultiChunkId, OutputStream)} 041 * must be used. The method emits a new implementation-specific {@link MultiChunk} 042 * to which new chunks can be added/written to. 043 * 044 * <li>When reading a multichunk from a file or input stream, the {@link #createMultiChunk(InputStream)} 045 * or {@link #createMultiChunk(File)} must be used. The emitted multichunk object can be read from. 046 * </ul> 047 * 048 * <p><b>Important:</b> Implementations must make sure that when providing a readable multichunk, 049 * the individual chunk objects must be randomly accessible. A sequential read (like with TAR, for 050 * instance), is not sufficient for the quick processing required in the application. 051 * 052 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 053 */ 054// TODO [low] The multichunk API is really odd; Think of something more sensible 055public abstract class MultiChunker { 056 057 /** 058 * Minimal multi chunk size in KB. 059 */ 060 public static final String PROPERTY_SIZE = "size"; 061 062 private static Logger logger = Logger.getLogger(MultiChunker.class.getSimpleName()); 063 064 protected int minMultiChunkSize; // in KB 065 066 /** 067 * Creates new multichunker without setting the minimum size of a multichunk. 068 */ 069 public MultiChunker() { 070 // Nothing. 071 } 072 073 /** 074 * Initializes the multichunker using a settings map. 075 * <br> 076 * Required settings are: 077 * <ul> 078 * <li> key: {@link #PROPERTY_SIZE}, value: integer encoded as String 079 * </ul> 080 */ 081 public void init(Map<String, String> settings) { 082 String size = settings.get(PROPERTY_SIZE); 083 084 if (size == null) { 085 logger.log(Level.SEVERE, String.format("Property %s must not be null.", PROPERTY_SIZE)); 086 throw new IllegalArgumentException(String.format("Property %s must not be null.", PROPERTY_SIZE)); 087 } 088 089 try { 090 this.minMultiChunkSize = Integer.parseInt(size); 091 } 092 catch (NumberFormatException nfe) { 093 logger.log(Level.SEVERE, String.format("Property %s could not be parsed as Integer.", PROPERTY_SIZE)); 094 throw new IllegalArgumentException(String.format("Property %s could not be parsed as Integer.", PROPERTY_SIZE)); 095 } 096 } 097 098 /** 099 * Creates a new multichunker, and sets the minimum size of a multichunk. 100 * 101 * <p>Implementations should react on the minimum multichunk size by allowing 102 * at least the given amount of KBs to be written to a multichunk, and declaring 103 * a multichunk 'full' if this limit is reached. 104 * 105 * @param minMultiChunkSize Minimum multichunk file size in kilo-bytes 106 */ 107 public MultiChunker(int minMultiChunkSize) { 108 this.minMultiChunkSize = minMultiChunkSize; 109 } 110 111 /** 112 * Create a new multichunk in <b>write mode</b>. 113 * 114 * <p>Using this method only allows writing to the returned multichunk. The resulting 115 * data will be written to the underlying output stream given in the parameter. 116 * 117 * @param id Identifier of the newly created multichunk 118 * @param os Underlying output stream to write the new multichunk to 119 * @return Returns a new multichunk object which can only be used for writing 120 * @throws IOException 121 */ 122 public abstract MultiChunk createMultiChunk(MultiChunkId id, OutputStream os) throws IOException; 123 124 /** 125 * Open existing multichunk in <b>read mode</b> using an underlying input stream. 126 * 127 * <p>Using this method only allows reading from the returned multichunk. The underlying 128 * input stream is opened and can be used to retrieve chunk data. 129 * 130 * @param is InputStream to initialize an existing multichunk for read-operations only 131 * @return Returns an existing multichunk object that allows read operations only 132 */ 133 public abstract MultiChunk createMultiChunk(InputStream is); 134 135 /** 136 * Open existing multichunk in <b>read mode</b> using an underlying file. 137 * 138 * <p>Using this method only allows reading from the returned multichunk. The underlying 139 * input stream is opened and can be used to retrieve chunk data. 140 * 141 * @param file File to read into a multichunk for read-operations only 142 * @return Returns an existing multichunk object that allows read operations only 143 */ 144 public abstract MultiChunk createMultiChunk(File file) throws IOException; 145 146 /** 147 * Returns a comprehensive string representation of a multichunker 148 */ 149 public abstract String toString(); 150 151 /** 152 * Instantiates a multichunker by its name using the default constructor. 153 * <br> 154 * After creating a new multichunker, it must be initialized using the 155 * {@link #init(Map) init()} method. The given type attribute is mapped to fully 156 * qualified class name (FQCN) of the form <code>org.syncany.chunk.XMultiChunker</code>, 157 * where <code>X</code> is the camel-cased type attribute. 158 * 159 * @param type Type/name of the multichunker (corresponds to its camel case class name) 160 * @return a new multichunker 161 */ 162 public static MultiChunker getInstance(String type) { 163 String thisPackage = MultiChunker.class.getPackage().getName(); 164 String camelCaseName = StringUtil.toCamelCase(type); 165 String fqClassName = thisPackage + "." + camelCaseName + MultiChunker.class.getSimpleName(); 166 167 // Try to load! 168 try { 169 Class<?> clazz = Class.forName(fqClassName); 170 return (MultiChunker) clazz.newInstance(); 171 } 172 catch (Exception ex) { 173 logger.log(Level.INFO, "Could not find multichunker FQCN " + fqClassName, ex); 174 return null; 175 } 176 } 177}