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}