View Javadoc
1   /*
2    * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4    * Copyright (C) 2009, Google Inc. and others
5    *
6    * This program and the accompanying materials are made available under the
7    * terms of the Eclipse Distribution License v. 1.0 which is available at
8    * https://www.eclipse.org/org/documents/edl-v10.php.
9    *
10   * SPDX-License-Identifier: BSD-3-Clause
11   */
12  
13  package org.eclipse.jgit.internal.storage.file;
14  
15  import java.io.EOFException;
16  import java.io.File;
17  import java.io.FileNotFoundException;
18  import java.io.FileOutputStream;
19  import java.io.FilterOutputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.nio.channels.Channels;
24  import java.text.MessageFormat;
25  import java.util.zip.Deflater;
26  import java.util.zip.DeflaterOutputStream;
27  
28  import org.eclipse.jgit.errors.ObjectWritingException;
29  import org.eclipse.jgit.internal.JGitText;
30  import org.eclipse.jgit.lib.Config;
31  import org.eclipse.jgit.lib.Constants;
32  import org.eclipse.jgit.lib.ObjectId;
33  import org.eclipse.jgit.lib.ObjectInserter;
34  import org.eclipse.jgit.lib.ObjectReader;
35  import org.eclipse.jgit.transport.PackParser;
36  import org.eclipse.jgit.util.FileUtils;
37  import org.eclipse.jgit.util.IO;
38  import org.eclipse.jgit.util.sha1.SHA1;
39  
40  /** Creates loose objects in a {@link ObjectDirectory}. */
41  class ObjectDirectoryInserter extends ObjectInserter {
42  	private final FileObjectDatabase db;
43  
44  	private final WriteConfig config;
45  
46  	private Deflater deflate;
47  
48  	ObjectDirectoryInserter(FileObjectDatabase dest, Config cfg) {
49  		db = dest;
50  		config = cfg.get(WriteConfig.KEY);
51  	}
52  
53  	/** {@inheritDoc} */
54  	@Override
55  	public ObjectId insert(int type, byte[] data, int off, int len)
56  			throws IOException {
57  		return insert(type, data, off, len, false);
58  	}
59  
60  	/**
61  	 * Insert a loose object into the database. If createDuplicate is true,
62  	 * write the loose object even if we already have it in the loose or packed
63  	 * ODB.
64  	 *
65  	 * @param type
66  	 * @param data
67  	 * @param off
68  	 * @param len
69  	 * @param createDuplicate
70  	 * @return ObjectId
71  	 * @throws IOException
72  	 */
73  	private ObjectId insert(
74  			int type, byte[] data, int off, int len, boolean createDuplicate)
75  			throws IOException {
76  		ObjectId id = idFor(type, data, off, len);
77  		if (!createDuplicate && db.has(id)) {
78  			return id;
79  		}
80  		File tmp = toTemp(type, data, off, len);
81  		return insertOneObject(tmp, id, createDuplicate);
82  	}
83  
84  	/** {@inheritDoc} */
85  	@Override
86  	public ObjectId insert(int type, long len, InputStream is)
87  			throws IOException {
88  		return insert(type, len, is, false);
89  	}
90  
91  	/**
92  	 * Insert a loose object into the database. If createDuplicate is true,
93  	 * write the loose object even if we already have it in the loose or packed
94  	 * ODB.
95  	 *
96  	 * @param type
97  	 * @param len
98  	 * @param is
99  	 * @param createDuplicate
100 	 * @return ObjectId
101 	 * @throws IOException
102 	 */
103 	ObjectId insert(int type, long len, InputStream is, boolean createDuplicate)
104 			throws IOException {
105 		if (len <= buffer().length) {
106 			byte[] buf = buffer();
107 			int actLen = IO.readFully(is, buf, 0);
108 			return insert(type, buf, 0, actLen, createDuplicate);
109 
110 		}
111 		SHA1 md = digest();
112 		File tmp = toTemp(md, type, len, is);
113 		ObjectId id = md.toObjectId();
114 		return insertOneObject(tmp, id, createDuplicate);
115 	}
116 
117 	private ObjectId insertOneObject(
118 			File tmp, ObjectId id, boolean createDuplicate)
119 			throws IOException, ObjectWritingException {
120 		switch (db.insertUnpackedObject(tmp, id, createDuplicate)) {
121 		case INSERTED:
122 		case EXISTS_PACKED:
123 		case EXISTS_LOOSE:
124 			return id;
125 
126 		case FAILURE:
127 		default:
128 			break;
129 		}
130 
131 		final File dst = db.fileFor(id);
132 		throw new ObjectWritingException(MessageFormat
133 				.format(JGitText.get().unableToCreateNewObject, dst));
134 	}
135 
136 	/** {@inheritDoc} */
137 	@Override
138 	public PackParser newPackParser(InputStream in) throws IOException {
139 		return new ObjectDirectoryPackParser(db, in);
140 	}
141 
142 	/** {@inheritDoc} */
143 	@Override
144 	public ObjectReader newReader() {
145 		return new WindowCursor(db, this);
146 	}
147 
148 	/** {@inheritDoc} */
149 	@Override
150 	public void flush() throws IOException {
151 		// Do nothing. Loose objects are immediately visible.
152 	}
153 
154 	/** {@inheritDoc} */
155 	@Override
156 	public void close() {
157 		if (deflate != null) {
158 			try {
159 				deflate.end();
160 			} finally {
161 				deflate = null;
162 			}
163 		}
164 	}
165 
166 	@SuppressWarnings("resource" /* java 7 */)
167 	private File toTemp(final SHA1 md, final int type, long len,
168 			final InputStream is) throws IOException, FileNotFoundException,
169 			Error {
170 		boolean delete = true;
171 		File tmp = newTempFile();
172 		try {
173 			FileOutputStream fOut = new FileOutputStream(tmp);
174 			try {
175 				OutputStream out = fOut;
176 				if (config.getFSyncObjectFiles())
177 					out = Channels.newOutputStream(fOut.getChannel());
178 				DeflaterOutputStream cOut = compress(out);
179 				SHA1OutputStream dOut = new SHA1OutputStream(cOut, md);
180 				writeHeader(dOut, type, len);
181 
182 				final byte[] buf = buffer();
183 				while (len > 0) {
184 					int n = is.read(buf, 0, (int) Math.min(len, buf.length));
185 					if (n <= 0)
186 						throw shortInput(len);
187 					dOut.write(buf, 0, n);
188 					len -= n;
189 				}
190 				dOut.flush();
191 				cOut.finish();
192 			} finally {
193 				if (config.getFSyncObjectFiles())
194 					fOut.getChannel().force(true);
195 				fOut.close();
196 			}
197 
198 			delete = false;
199 			return tmp;
200 		} finally {
201 			if (delete)
202 				FileUtils.delete(tmp, FileUtils.RETRY);
203 		}
204 	}
205 
206 	@SuppressWarnings("resource" /* java 7 */)
207 	private File toTemp(final int type, final byte[] buf, final int pos,
208 			final int len) throws IOException, FileNotFoundException {
209 		boolean delete = true;
210 		File tmp = newTempFile();
211 		try {
212 			FileOutputStream fOut = new FileOutputStream(tmp);
213 			try {
214 				OutputStream out = fOut;
215 				if (config.getFSyncObjectFiles())
216 					out = Channels.newOutputStream(fOut.getChannel());
217 				DeflaterOutputStream cOut = compress(out);
218 				writeHeader(cOut, type, len);
219 				cOut.write(buf, pos, len);
220 				cOut.finish();
221 			} finally {
222 				if (config.getFSyncObjectFiles())
223 					fOut.getChannel().force(true);
224 				fOut.close();
225 			}
226 
227 			delete = false;
228 			return tmp;
229 		} finally {
230 			if (delete)
231 				FileUtils.delete(tmp, FileUtils.RETRY);
232 		}
233 	}
234 
235 	void writeHeader(OutputStream out, int type, long len)
236 			throws IOException {
237 		out.write(Constants.encodedTypeString(type));
238 		out.write((byte) ' ');
239 		out.write(Constants.encodeASCII(len));
240 		out.write((byte) 0);
241 	}
242 
243 	File newTempFile() throws IOException {
244 		return File.createTempFile("noz", null, db.getDirectory()); //$NON-NLS-1$
245 	}
246 
247 	DeflaterOutputStream compress(OutputStream out) {
248 		if (deflate == null)
249 			deflate = new Deflater(config.getCompression());
250 		else
251 			deflate.reset();
252 		return new DeflaterOutputStream(out, deflate, 8192);
253 	}
254 
255 	private static EOFException shortInput(long missing) {
256 		return new EOFException(MessageFormat.format(
257 				JGitText.get().inputDidntMatchLength, Long.valueOf(missing)));
258 	}
259 
260 	private static class SHA1OutputStream extends FilterOutputStream {
261 		private final SHA1 md;
262 
263 		SHA1OutputStream(OutputStream out, SHA1 md) {
264 			super(out);
265 			this.md = md;
266 		}
267 
268 		@Override
269 		public void write(int b) throws IOException {
270 			md.update((byte) b);
271 			out.write(b);
272 		}
273 
274 		@Override
275 		public void write(byte[] in, int p, int n) throws IOException {
276 			md.update(in, p, n);
277 			out.write(in, p, n);
278 		}
279 	}
280 }