blob: dd03abcccc563cab05a9401886efe44ebc4076f3 [file] [log] [blame]
// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.schema.backup;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gwtorm.client.Access;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.protobuf.InvalidProtocolBufferException;
import org.eclipse.jgit.util.IO;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/** Restore the database from a compressed series of protobuf objects. */
public class RestoreBackup {
public static void restore(InputStream in, ReviewDb dst) throws IOException,
OrmException {
in = new BufferedInputStream(new GZIPInputStream(in, 8192));
BackupDatabase<ReviewDb> bck = new BackupDatabase<ReviewDb>(ReviewDb.class);
ReviewDb src = bck.open();
dst.setAutoFlush(false);
restoreImpl(in, src, dst);
dst.flush();
}
@SuppressWarnings("unchecked")
private static void restoreImpl(InputStream in, ReviewDb src, ReviewDb dst)
throws IOException, OrmException {
// Remove every row, we're about to overwrite them all.
//
for (Access<?, ?> s : dst.allRelations()) {
List objects = s.iterateAllEntities().toList();
s.delete(objects);
}
Map<Integer, BackupAccess<?, ?>> read = index(src);
Map<Integer, Access<?, ?>> store = index(dst);
// The first object should be a Counters.
//
Counters cnts = Counters.CODEC.decodeWithSize(in);
// Remaining objects are length delimited until EOF.
//
Set<Integer> notKnown = new HashSet<Integer>();
for (;;) {
in.mark(1);
if (in.read() == -1) {
break;
}
in.reset();
in.mark(32);
int len = readRawVarint32(in);
int id = readRawVarint32(in) >>> 3;
BackupAccess<?, ?> r = read.get(id);
Access<?, ?> w = store.get(id);
if (r != null && w != null) {
in.reset();
ProtobufCodec pc = r.getObjectCodec();
Set s = Collections.singleton(pc.decodeWithSize(in));
w.upsert(s);
} else {
if (notKnown.add(id)) {
System.err.println("warning: Skipping relation " + id);
}
in.reset();
if (len != readRawVarint32(in)) {
throw new IOException("Stream didn't reset before skipping");
}
IO.skipFully(in, len);
}
}
}
@SuppressWarnings("unchecked")
private static Map index(ReviewDb src) {
Map<Integer, Access<?, ?>> relations = new HashMap<Integer, Access<?, ?>>();
for (Access<?, ?> a : src.allRelations()) {
relations.put(a.getRelationID(), a);
}
return relations;
}
private static int readRawVarint32(InputStream in) throws IOException {
int b = in.read();
if (b == -1) {
throw new InvalidProtocolBufferException("Truncated input");
}
if ((b & 0x80) == 0) {
return b;
}
int result = b & 0x7f;
int offset = 7;
for (; offset < 32; offset += 7) {
b = in.read();
if (b == -1) {
throw new InvalidProtocolBufferException("Truncated input");
}
result |= (b & 0x7f) << offset;
if ((b & 0x80) == 0) {
return result;
}
}
// Keep reading up to 64 bits.
for (; offset < 64; offset += 7) {
b = in.read();
if (b == -1) {
throw new InvalidProtocolBufferException("Truncated input");
}
if ((b & 0x80) == 0) {
return result;
}
}
throw new InvalidProtocolBufferException("Malformed varint");
}
private RestoreBackup() {
}
}