/**
* This file is part of git-as-svn. It is subject to the license terms
* in the LICENSE file found in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn,
* including this file, may be copied, modified, propagated, or distributed
* except according to the terms contained in the LICENSE file.
*/
package svnserver.ext.gitlfs.filter;
import com.google.common.io.ByteStreams;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectStream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mapdb.DB;
import org.tmatesoft.svn.core.SVNException;
import ru.bozaro.gitlfs.pointer.Constants;
import ru.bozaro.gitlfs.pointer.Pointer;
import svnserver.auth.User;
import svnserver.context.LocalContext;
import svnserver.ext.gitlfs.config.LfsConfig;
import svnserver.ext.gitlfs.server.LfsServer;
import svnserver.ext.gitlfs.server.LfsServerEntry;
import svnserver.ext.gitlfs.storage.LfsReader;
import svnserver.ext.gitlfs.storage.LfsStorage;
import svnserver.ext.gitlfs.storage.LfsWriter;
import svnserver.repository.SvnForbiddenException;
import svnserver.repository.git.GitObject;
import svnserver.repository.git.filter.GitFilter;
import svnserver.repository.git.filter.GitFilterHelper;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
/**
* Filter for Git LFS.
*
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
*/
public class LfsFilter implements GitFilter {
@NotNull
public static final String NAME = "lfs";
@NotNull
private final LfsStorage storage;
@NotNull
private final DB cacheDb;
public LfsFilter(@NotNull LocalContext context) throws IOException, SVNException {
this.storage = LfsConfig.getStorage(context);
this.cacheDb = context.getShared().getCacheDB();
final LfsServer lfsServer = context.getShared().get(LfsServer.class);
if (lfsServer != null) {
context.add(LfsServerEntry.class, new LfsServerEntry(lfsServer, context, storage));
}
}
@NotNull
@Override
public String getName() {
return NAME;
}
@NotNull
private LfsReader getReader(@NotNull Map<String, String> pointer) throws IOException {
final LfsReader reader = storage.getReader(pointer.get(Constants.OID));
if (reader == null) {
throw new SvnForbiddenException();
}
return reader;
}
@NotNull
@Override
public String getMd5(@NotNull GitObject<? extends ObjectId> objectId) throws IOException, SVNException {
final ObjectLoader loader = objectId.openObject();
final ObjectStream stream = loader.openStream();
final byte[] header = new byte[Constants.POINTER_MAX_SIZE];
int length = ByteStreams.read(stream, header, 0, header.length);
if (length < header.length) {
final Map<String, String> pointer = Pointer.parsePointer(header, 0, length);
if (pointer != null) {
String md5 = getReader(pointer).getMd5();
if (md5 != null) {
return md5;
}
}
}
return GitFilterHelper.getMd5(this, cacheDb, objectId, false);
}
@Override
public long getSize(@NotNull GitObject<? extends ObjectId> objectId) throws IOException, SVNException {
final ObjectLoader loader = objectId.openObject();
final ObjectStream stream = loader.openStream();
final byte[] header = new byte[Constants.POINTER_MAX_SIZE];
int length = ByteStreams.read(stream, header, 0, header.length);
if (length < header.length) {
final Map<String, String> pointer = Pointer.parsePointer(header, 0, length);
if (pointer != null) {
return getReader(pointer).getSize();
}
}
return loader.getSize();
}
@NotNull
@Override
public InputStream inputStream(@NotNull GitObject<? extends ObjectId> objectId) throws IOException, SVNException {
final ObjectLoader loader = objectId.openObject();
final ObjectStream stream = loader.openStream();
final byte[] header = new byte[Constants.POINTER_MAX_SIZE];
int length = ByteStreams.read(stream, header, 0, header.length);
if (length < header.length) {
final Map<String, String> pointer = Pointer.parsePointer(header, 0, length);
if (pointer != null) {
return getReader(pointer).openStream();
}
}
return new TemporaryInputStream(header, length, stream);
}
@NotNull
@Override
public OutputStream outputStream(@NotNull OutputStream stream, @Nullable User user) throws IOException, SVNException {
return new TemporaryOutputStream(storage.getWriter(user), stream);
}
private static class TemporaryInputStream extends InputStream {
@NotNull
private final byte[] header;
@NotNull
private final InputStream stream;
private final int length;
private int offset = 0;
private TemporaryInputStream(@NotNull byte[] header, int length, @NotNull InputStream stream) throws FileNotFoundException {
this.header = header;
this.length = length;
this.stream = stream;
}
@Override
public int read() throws IOException {
if (offset < length) {
//noinspection MagicNumber
return header[offset++] & 0xff;
}
return stream.read();
}
@Override
public int read(@NotNull byte[] buf, int off, int len) throws IOException {
if (len == 0) {
return 0;
}
if (this.offset < length) {
final int count = Math.min(len, length - this.offset);
System.arraycopy(header, offset, buf, off, count);
offset += count;
return count;
}
return stream.read(buf, off, len);
}
@Override
public void close() throws IOException {
stream.close();
}
}
private static class TemporaryOutputStream extends OutputStream {
private final LfsWriter writer;
private final OutputStream stream;
private long size;
public TemporaryOutputStream(LfsWriter writer, OutputStream stream) {
this.writer = writer;
this.stream = stream;
size = 0;
}
@Override
public void write(int b) throws IOException {
writer.write(b);
size++;
}
@Override
public void write(@NotNull byte[] b, int off, int len) throws IOException {
writer.write(b, off, len);
size += len;
}
@Override
public void flush() throws IOException {
writer.flush();
}
@Override
public void close() throws IOException {
final Map<String, String> pointer = Pointer.createPointer(writer.finish(null), size);
writer.close();
stream.write(Pointer.serializePointer(pointer));
stream.close();
}
}
}