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