Inline Edit: Acquire content and type in one request

BinaryResult can return X-FYI-Content-Type header with the
real content type. Use this inside of FileContentUtil to
send back the actual type in one request.

By using a single request the server only needs to load
object data into memory once, to determine type and to
stream the content to the client.

Move base64 decoding into RestApi. The format is very
predictable from the server and can be applied to any
REST API call made from the browser.

Change-Id: I3b6c83efd0eb76148a3ac3e50cd2e3e4c37f08c5
This commit is contained in:
Shawn Pearce
2015-01-01 23:18:50 -05:00
parent ebc1ea909e
commit 38df42f051
18 changed files with 449 additions and 229 deletions

View File

@@ -59,6 +59,7 @@ import com.google.inject.Inject;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils; import org.apache.commons.codec.binary.StringUtils;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@@ -194,10 +195,10 @@ public class ChangeEditIT extends AbstractDaemonTest {
Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen(); Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen();
modifier.rebaseEdit(edit, current); modifier.rebaseEdit(edit, current);
edit = editUtil.byChange(change).get(); edit = editUtil.byChange(change).get();
assertByteArray(fileUtil.getContent(edit.getChange().getProject(), edit assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
.getRevision().get(), FILE_NAME), CONTENT_NEW); ObjectId.fromString(edit.getRevision().get()), FILE_NAME), CONTENT_NEW);
assertByteArray(fileUtil.getContent(edit.getChange().getProject(), edit assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
.getRevision().get(), FILE_NAME2), CONTENT_NEW2); ObjectId.fromString(edit.getRevision().get()), FILE_NAME2), CONTENT_NEW2);
assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo( assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(
current.getPatchSetId()); current.getPatchSetId());
Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen(); Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
@@ -218,10 +219,10 @@ public class ChangeEditIT extends AbstractDaemonTest {
RestResponse r = adminSession.post(urlRebase()); RestResponse r = adminSession.post(urlRebase());
assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
edit = editUtil.byChange(change).get(); edit = editUtil.byChange(change).get();
assertByteArray(fileUtil.getContent(edit.getChange().getProject(), edit assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
.getRevision().get(), FILE_NAME), CONTENT_NEW); ObjectId.fromString(edit.getRevision().get()), FILE_NAME), CONTENT_NEW);
assertByteArray(fileUtil.getContent(edit.getChange().getProject(), edit assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
.getRevision().get(), FILE_NAME2), CONTENT_NEW2); ObjectId.fromString(edit.getRevision().get()), FILE_NAME2), CONTENT_NEW2);
assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo( assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(
current.getPatchSetId()); current.getPatchSetId());
Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen(); Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
@@ -235,9 +236,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW))) assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
.isEqualTo(RefUpdate.Result.FORCED); .isEqualTo(RefUpdate.Result.FORCED);
edit = editUtil.byChange(change); edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
.getRevision().get(), FILE_NAME), CONTENT_NEW);
editUtil.delete(edit.get()); editUtil.delete(edit.get());
edit = editUtil.byChange(change); edit = editUtil.byChange(change);
assertThat(edit.isPresent()).isFalse(); assertThat(edit.isPresent()).isFalse();
@@ -364,8 +364,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
RefUpdate.Result.FORCED); RefUpdate.Result.FORCED);
edit = editUtil.byChange(change); edit = editUtil.byChange(change);
try { try {
fileUtil.getContent(edit.get().getChange().getProject(), fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
edit.get().getRevision().get(), FILE_NAME); ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
fail("ResourceNotFoundException expected"); fail("ResourceNotFoundException expected");
} catch (ResourceNotFoundException rnfe) { } catch (ResourceNotFoundException rnfe) {
} }
@@ -377,8 +377,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
Optional<ChangeEdit> edit = editUtil.byChange(change); Optional<ChangeEdit> edit = editUtil.byChange(change);
try { try {
fileUtil.getContent(edit.get().getChange().getProject(), fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
edit.get().getRevision().get(), FILE_NAME); ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
fail("ResourceNotFoundException expected"); fail("ResourceNotFoundException expected");
} catch (ResourceNotFoundException rnfe) { } catch (ResourceNotFoundException rnfe) {
} }
@@ -397,8 +397,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
SC_NO_CONTENT); SC_NO_CONTENT);
Optional<ChangeEdit> edit = editUtil.byChange(change); Optional<ChangeEdit> edit = editUtil.byChange(change);
try { try {
fileUtil.getContent(edit.get().getChange().getProject(), fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
edit.get().getRevision().get(), FILE_NAME); ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
fail("ResourceNotFoundException expected"); fail("ResourceNotFoundException expected");
} catch (ResourceNotFoundException rnfe) { } catch (ResourceNotFoundException rnfe) {
} }
@@ -412,9 +412,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(modifier.restoreFile(edit.get(), FILE_NAME)).isEqualTo( assertThat(modifier.restoreFile(edit.get(), FILE_NAME)).isEqualTo(
RefUpdate.Result.FORCED); RefUpdate.Result.FORCED);
edit = editUtil.byChange(change2); edit = editUtil.byChange(change2);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
.getRevision().get(), FILE_NAME), CONTENT_OLD);
} }
@Test @Test
@@ -424,9 +423,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(adminSession.post(urlEdit2(), in).getStatusCode()).isEqualTo( assertThat(adminSession.post(urlEdit2(), in).getStatusCode()).isEqualTo(
SC_NO_CONTENT); SC_NO_CONTENT);
Optional<ChangeEdit> edit = editUtil.byChange(change2); Optional<ChangeEdit> edit = editUtil.byChange(change2);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
.getRevision().get(), FILE_NAME), CONTENT_OLD);
} }
@Test @Test
@@ -436,15 +434,13 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW))) assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
.isEqualTo(RefUpdate.Result.FORCED); .isEqualTo(RefUpdate.Result.FORCED);
edit = editUtil.byChange(change); edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
.getRevision().get(), FILE_NAME), CONTENT_NEW);
assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2))) assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2)))
.isEqualTo(RefUpdate.Result.FORCED); .isEqualTo(RefUpdate.Result.FORCED);
edit = editUtil.byChange(change); edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW2);
.getRevision().get(), FILE_NAME), CONTENT_NEW2);
} }
@Test @Test
@@ -454,16 +450,14 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()) assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
.isEqualTo(SC_NO_CONTENT); .isEqualTo(SC_NO_CONTENT);
Optional<ChangeEdit> edit = editUtil.byChange(change); Optional<ChangeEdit> edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
.getRevision().get(), FILE_NAME), CONTENT_NEW);
in.content = RestSession.newRawInput(CONTENT_NEW2); in.content = RestSession.newRawInput(CONTENT_NEW2);
assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()) assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
.isEqualTo(SC_NO_CONTENT); .isEqualTo(SC_NO_CONTENT);
edit = editUtil.byChange(change); edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW2);
.getRevision().get(), FILE_NAME), CONTENT_NEW2);
} }
@Test @Test
@@ -474,9 +468,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()) assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
.isEqualTo(SC_NO_CONTENT); .isEqualTo(SC_NO_CONTENT);
Optional<ChangeEdit> edit = editUtil.byChange(change); Optional<ChangeEdit> edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
.getRevision().get(), FILE_NAME), CONTENT_NEW);
} }
@Test @Test
@@ -485,9 +478,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(adminSession.put(urlEditFile()).getStatusCode()).isEqualTo( assertThat(adminSession.put(urlEditFile()).getStatusCode()).isEqualTo(
SC_NO_CONTENT); SC_NO_CONTENT);
Optional<ChangeEdit> edit = editUtil.byChange(change); Optional<ChangeEdit> edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), "".getBytes());
.getRevision().get(), FILE_NAME), "".getBytes());
} }
@Test @Test
@@ -495,9 +487,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(adminSession.post(urlEdit()).getStatusCode()).isEqualTo( assertThat(adminSession.post(urlEdit()).getStatusCode()).isEqualTo(
SC_NO_CONTENT); SC_NO_CONTENT);
Optional<ChangeEdit> edit = editUtil.byChange(change); Optional<ChangeEdit> edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
.getRevision().get(), FILE_NAME), CONTENT_OLD);
} }
@Test @Test
@@ -536,8 +527,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
SC_NO_CONTENT); SC_NO_CONTENT);
Optional<ChangeEdit> edit = editUtil.byChange(change); Optional<ChangeEdit> edit = editUtil.byChange(change);
try { try {
fileUtil.getContent(edit.get().getChange().getProject(), fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
edit.get().getRevision().get(), FILE_NAME); ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
fail("ResourceNotFoundException expected"); fail("ResourceNotFoundException expected");
} catch (ResourceNotFoundException rnfe) { } catch (ResourceNotFoundException rnfe) {
} }
@@ -552,9 +543,8 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW))) assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW)))
.isEqualTo(RefUpdate.Result.FORCED); .isEqualTo(RefUpdate.Result.FORCED);
edit = editUtil.byChange(change); edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW);
.getRevision().get(), FILE_NAME2), CONTENT_NEW);
} }
@Test @Test
@@ -564,15 +554,13 @@ public class ChangeEditIT extends AbstractDaemonTest {
assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW))) assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW)))
.isEqualTo(RefUpdate.Result.FORCED); .isEqualTo(RefUpdate.Result.FORCED);
edit = editUtil.byChange(change); edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW);
.getRevision().get(), FILE_NAME2), CONTENT_NEW);
assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW2))) assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW2)))
.isEqualTo(RefUpdate.Result.FORCED); .isEqualTo(RefUpdate.Result.FORCED);
edit = editUtil.byChange(change); edit = editUtil.byChange(change);
assertByteArray( assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
fileUtil.getContent(edit.get().getChange().getProject(), edit.get() ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW2);
.getRevision().get(), FILE_NAME2), CONTENT_NEW2);
} }
@Test @Test

View File

@@ -16,6 +16,9 @@ package com.google.gerrit.client.change;
import com.google.gerrit.client.changes.ChangeFileApi; import com.google.gerrit.client.changes.ChangeFileApi;
import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.HttpCallback;
import com.google.gerrit.client.rpc.HttpResponse;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurEvent;
@@ -51,10 +54,10 @@ class FileTextBox extends NpTextBox {
} }
private void loadFileContent() { private void loadFileContent() {
ChangeFileApi.getContent(id, getText(), new GerritCallback<String>() { ChangeFileApi.getContent(id, getText(), new HttpCallback<NativeString>() {
@Override @Override
public void onSuccess(String result) { public void onSuccess(HttpResponse<NativeString> result) {
textArea.setText(result); textArea.setText(result.getResult().asString());
} }
@Override @Override
@@ -62,7 +65,7 @@ class FileTextBox extends NpTextBox {
if (RestApi.isNotFound(caught)) { if (RestApi.isNotFound(caught)) {
// that means that the file doesn't exist in the repository // that means that the file doesn't exist in the repository
} else { } else {
super.onFailure(caught); GerritCallback.showFailure(caught);
} }
} }
}); });

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.client.changes;
import com.google.gerrit.client.VoidResult; import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.HttpCallback;
import com.google.gerrit.client.rpc.NativeString; import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
@@ -29,52 +30,10 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
* files in a change. * files in a change.
*/ */
public class ChangeFileApi { public class ChangeFileApi {
static abstract class CallbackWrapper<I, O> implements AsyncCallback<I> {
protected AsyncCallback<O> wrapped;
public CallbackWrapper(AsyncCallback<O> callback) {
wrapped = callback;
}
@Override
public abstract void onSuccess(I result);
@Override
public void onFailure(Throwable caught) {
wrapped.onFailure(caught);
}
}
private static CallbackWrapper<NativeString, String> wrapper(
AsyncCallback<String> cb) {
return new CallbackWrapper<NativeString, String>(cb) {
@Override
public void onSuccess(NativeString b64) {
if (b64 != null) {
wrapped.onSuccess(b64decode(b64.asString()));
}
}
};
}
/** Get the contents of a File in a PatchSet or change edit. */ /** Get the contents of a File in a PatchSet or change edit. */
public static void getContent(PatchSet.Id id, String filename, public static void getContent(PatchSet.Id id, String filename,
AsyncCallback<String> cb) { HttpCallback<NativeString> cb) {
contentEditOrPs(id, filename).get(wrapper(cb)); contentEditOrPs(id, filename).get(cb);
}
/** Get the content type of a File in a PatchSet or change edit. */
public static void getContentType(PatchSet.Id id, String filename,
AsyncCallback<String> cb) {
contentTypeEditOrPs(id, filename).get(
new CallbackWrapper<NativeString, String>(cb) {
@Override
public void onSuccess(NativeString str) {
if (str != null) {
wrapped.onSuccess(str.asString());
}
}
});
} }
/** /**
@@ -82,11 +41,11 @@ public class ChangeFileApi {
* edit. * edit.
**/ **/
public static void getContentOrMessage(PatchSet.Id id, String path, public static void getContentOrMessage(PatchSet.Id id, String path,
AsyncCallback<String> cb) { HttpCallback<NativeString> cb) {
RestApi api = (Patch.COMMIT_MSG.equals(path) && id.get() == 0) RestApi api = (Patch.COMMIT_MSG.equals(path) && id.get() == 0)
? messageEdit(id) ? messageEdit(id)
: contentEditOrPs(id, path); : contentEditOrPs(id, path);
api.get(wrapper(cb)); api.get(cb);
} }
/** Put contents into a File in a change edit. */ /** Put contents into a File in a change edit. */
@@ -141,18 +100,10 @@ public class ChangeFileApi {
return ChangeApi.change(id.getParentKey().get()).view("edit:message"); return ChangeApi.change(id.getParentKey().get()).view("edit:message");
} }
private static RestApi contentTypeEditOrPs(PatchSet.Id id, String filename) {
return id.get() == 0
? contentEdit(id.getParentKey(), filename).view("type")
: ChangeApi.revision(id).view("files").id(filename).view("type");
}
private static RestApi contentEdit(Change.Id id, String filename) { private static RestApi contentEdit(Change.Id id, String filename) {
return ChangeApi.edit(id.get()).id(filename); return ChangeApi.edit(id.get()).id(filename);
} }
private static native String b64decode(String a) /*-{ return window.atob(a); }-*/;
private static class Input extends JavaScriptObject { private static class Input extends JavaScriptObject {
final native void restore_path(String p) /*-{ if(p)this.restore_path=p; }-*/; final native void restore_path(String p) /*-{ if(p)this.restore_path=p; }-*/;

View File

@@ -25,6 +25,9 @@ import com.google.gerrit.client.diff.FileInfo;
import com.google.gerrit.client.diff.Header; import com.google.gerrit.client.diff.Header;
import com.google.gerrit.client.rpc.CallbackGroup; import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.HttpCallback;
import com.google.gerrit.client.rpc.HttpResponse;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.ScreenLoadCallback; import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.Screen; import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks; import com.google.gerrit.common.PageLinks;
@@ -68,7 +71,7 @@ public class EditScreen extends Screen {
private final String path; private final String path;
private DiffPreferences prefs; private DiffPreferences prefs;
private CodeMirror cm; private CodeMirror cm;
private String type; private HttpResponse<NativeString> content;
@UiField Element header; @UiField Element header;
@UiField Element project; @UiField Element project;
@@ -116,20 +119,6 @@ public class EditScreen extends Screen {
} }
})); }));
if (prefs.syntaxHighlighting() && !Patch.COMMIT_MSG.equals(path)) {
final AsyncCallback<Void> modeInjectorCb = group.addEmpty();
ChangeFileApi.getContentType(revision, path,
cmGroup.add(new GerritCallback<String>() {
@Override
public void onSuccess(String result) {
ModeInfo mode = ModeInfo.findMode(result, path);
type = mode != null ? mode.mime() : null;
injectMode(result, modeInjectorCb);
}
}));
}
cmGroup.done();
ChangeApi.detail(revision.getParentKey().get(), ChangeApi.detail(revision.getParentKey().get(),
group.add(new GerritCallback<ChangeInfo>() { group.add(new GerritCallback<ChangeInfo>() {
@Override @Override
@@ -140,12 +129,34 @@ public class EditScreen extends Screen {
})); }));
ChangeFileApi.getContentOrMessage(revision, path, ChangeFileApi.getContentOrMessage(revision, path,
group.addFinal(new ScreenLoadCallback<String>(this) { cmGroup.add(new HttpCallback<NativeString>() {
final AsyncCallback<Void> modeCallback = group.addEmpty();
@Override @Override
protected void preDisplay(String content) { public void onSuccess(HttpResponse<NativeString> fc) {
initEditor(content); content = fc;
if (prefs.syntaxHighlighting()) {
injectMode(fc.getContentType(), modeCallback);
} else {
modeCallback.onSuccess(null);
}
}
@Override
public void onFailure(Throwable e) {
GerritCallback.showFailure(e);
} }
})); }));
group.addListener(new ScreenLoadCallback<Void>(this) {
@Override
protected void preDisplay(Void result) {
initEditor(content);
content = null;
}
});
cmGroup.done();
group.done();
} }
@Override @Override
@@ -223,12 +234,12 @@ public class EditScreen extends Screen {
Gerrit.display(PageLinks.toChangeInEditMode(revision.getParentKey())); Gerrit.display(PageLinks.toChangeInEditMode(revision.getParentKey()));
} }
private void initEditor(String content) { private void initEditor(HttpResponse<NativeString> file) {
ModeInfo mode = prefs.syntaxHighlighting() ModeInfo mode = prefs.syntaxHighlighting()
? ModeInfo.findMode(type, path) ? ModeInfo.findMode(file.getContentType(), path)
: null; : null;
cm = CodeMirror.create(editor, Configuration.create() cm = CodeMirror.create(editor, Configuration.create()
.set("value", content) .set("value", file.getResult().asString())
.set("readOnly", false) .set("readOnly", false)
.set("cursorBlinkRate", 0) .set("cursorBlinkRate", 0)
.set("cursorHeight", 0.85) .set("cursorHeight", 0.85)

View File

@@ -42,8 +42,8 @@ import java.util.Set;
* processing it. * processing it.
*/ */
public class CallbackGroup { public class CallbackGroup {
private final List<CallbackImpl<?>> callbacks; private final List<CallbackGlue> callbacks;
private final Set<CallbackImpl<?>> remaining; private final Set<CallbackGlue> remaining;
private boolean finalAdded; private boolean finalAdded;
private boolean failed; private boolean failed;
@@ -76,6 +76,27 @@ public class CallbackGroup {
return handleAdd(cb); return handleAdd(cb);
} }
public <T> HttpCallback<T> add(HttpCallback<T> cb) {
checkFinalAdded();
if (failed) {
cb.onFailure(failedThrowable);
return new HttpCallback<T>() {
@Override
public void onSuccess(HttpResponse<T> result) {
}
@Override
public void onFailure(Throwable caught) {
}
};
}
HttpCallbackImpl<T> w = new HttpCallbackImpl<>(cb);
callbacks.add(w);
remaining.add(w);
return w;
}
public <T> Callback<T> addFinal(final AsyncCallback<T> cb) { public <T> Callback<T> addFinal(final AsyncCallback<T> cb) {
checkFinalAdded(); checkFinalAdded();
finalAdded = true; finalAdded = true;
@@ -84,7 +105,7 @@ public class CallbackGroup {
public void done() { public void done() {
finalAdded = true; finalAdded = true;
applyAllSuccess(); apply();
} }
public void addListener(AsyncCallback<Void> cb) { public void addListener(AsyncCallback<Void> cb) {
@@ -99,20 +120,31 @@ public class CallbackGroup {
addListener(group.<Void> addEmpty()); addListener(group.<Void> addEmpty());
} }
private void applyAllSuccess() { private void success(CallbackGlue cb) {
if (!failed && finalAdded && remaining.isEmpty()) { remaining.remove(cb);
for (CallbackImpl<?> cb : callbacks) { apply();
cb.applySuccess();
}
callbacks.clear();
}
} }
private void applyAllFailed() { private <T> void failure(CallbackGlue w, Throwable caught) {
if (failed && finalAdded && remaining.isEmpty()) { if (!failed) {
for (CallbackImpl<?> cb : callbacks) { failed = true;
failedThrowable = caught;
}
remaining.remove(w);
apply();
}
private void apply() {
if (finalAdded && remaining.isEmpty()) {
if (failed) {
for (CallbackGlue cb : callbacks) {
cb.applyFailed(); cb.applyFailed();
} }
} else {
for (CallbackGlue cb : callbacks) {
cb.applySuccess();
}
}
callbacks.clear(); callbacks.clear();
} }
} }
@@ -139,7 +171,12 @@ public class CallbackGroup {
extends AsyncCallback<T>, com.google.gwtjsonrpc.common.AsyncCallback<T> { extends AsyncCallback<T>, com.google.gwtjsonrpc.common.AsyncCallback<T> {
} }
private class CallbackImpl<T> implements Callback<T> { private interface CallbackGlue {
void applySuccess();
void applyFailed();
}
private class CallbackImpl<T> implements Callback<T>, CallbackGlue {
AsyncCallback<T> delegate; AsyncCallback<T> delegate;
T result; T result;
@@ -150,21 +187,16 @@ public class CallbackGroup {
@Override @Override
public void onSuccess(T value) { public void onSuccess(T value) {
this.result = value; this.result = value;
remaining.remove(this); success(this);
CallbackGroup.this.applyAllSuccess();
} }
@Override @Override
public void onFailure(Throwable caught) { public void onFailure(Throwable caught) {
if (!failed) { failure(this, caught);
failed = true;
failedThrowable = caught;
}
remaining.remove(this);
CallbackGroup.this.applyAllFailed();
} }
void applySuccess() { @Override
public void applySuccess() {
AsyncCallback<T> cb = delegate; AsyncCallback<T> cb = delegate;
if (cb != null) { if (cb != null) {
delegate = null; delegate = null;
@@ -173,7 +205,8 @@ public class CallbackGroup {
} }
} }
void applyFailed() { @Override
public void applyFailed() {
AsyncCallback<T> cb = delegate; AsyncCallback<T> cb = delegate;
if (cb != null) { if (cb != null) {
delegate = null; delegate = null;
@@ -182,4 +215,44 @@ public class CallbackGroup {
} }
} }
} }
private class HttpCallbackImpl<T> implements HttpCallback<T>, CallbackGlue {
private HttpCallback<T> delegate;
private HttpResponse<T> result;
HttpCallbackImpl(HttpCallback<T> delegate) {
this.delegate = delegate;
}
@Override
public void onSuccess(HttpResponse<T> result) {
this.result = result;
success(this);
}
@Override
public void onFailure(Throwable caught) {
failure(this, caught);
}
@Override
public void applySuccess() {
HttpCallback<T> cb = delegate;
if (cb != null) {
delegate = null;
cb.onSuccess(result);
result = null;
}
}
@Override
public void applyFailed() {
HttpCallback<T> cb = delegate;
if (cb != null) {
delegate = null;
result = null;
cb.onFailure(failedThrowable);
}
}
}
} }

View File

@@ -23,7 +23,6 @@ import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.common.errors.NoSuchEntityException; import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.common.errors.NoSuchGroupException; import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.common.errors.NotSignedInException; import com.google.gerrit.common.errors.NotSignedInException;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.InvocationException; import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwtjsonrpc.client.RemoteJsonException; import com.google.gwtjsonrpc.client.RemoteJsonException;
import com.google.gwtjsonrpc.client.ServerUnavailableException; import com.google.gwtjsonrpc.client.ServerUnavailableException;
@@ -35,6 +34,10 @@ public abstract class GerritCallback<T> implements
com.google.gwt.user.client.rpc.AsyncCallback<T> { com.google.gwt.user.client.rpc.AsyncCallback<T> {
@Override @Override
public void onFailure(final Throwable caught) { public void onFailure(final Throwable caught) {
showFailure(caught);
}
public static void showFailure(Throwable caught) {
if (isNotSignedIn(caught) || isInvalidXSRF(caught)) { if (isNotSignedIn(caught) || isInvalidXSRF(caught)) {
new NotSignedInDialog().center(); new NotSignedInDialog().center();
@@ -70,7 +73,6 @@ public abstract class GerritCallback<T> implements
new ErrorDialog(RpcConstants.C.errorServerUnavailable()).center(); new ErrorDialog(RpcConstants.C.errorServerUnavailable()).center();
} else { } else {
GWT.log(getClass().getName() + " caught " + caught, caught);
new ErrorDialog(caught).center(); new ErrorDialog(caught).center();
} }
} }

View File

@@ -0,0 +1,21 @@
// Copyright (C) 2015 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.client.rpc;
/** AsyncCallback supplied with HTTP response headers. */
public interface HttpCallback<T> {
void onSuccess(HttpResponse<T> result);
void onFailure(Throwable caught);
}

View File

@@ -0,0 +1,56 @@
// Copyright (C) 2015 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.client.rpc;
import com.google.gwt.http.client.Response;
/** Wraps decoded server reply with HTTP headers. */
public class HttpResponse<T> {
private final Response httpResponse;
private final String contentType;
private final T result;
HttpResponse(Response httpResponse, String contentType, T result) {
this.httpResponse = httpResponse;
this.contentType = contentType;
this.result = result;
}
/** HTTP status code, always in the 2xx family. */
public int getStatusCode() {
return httpResponse.getStatusCode();
}
/**
* Content type supplied by the server.
*
* This helper simplifies the common {@code getHeader("Content-Type")} case.
*/
public String getContentType() {
return contentType;
}
/** Lookup an arbitrary reply header. */
public String getHeader(String header) {
if ("Content-Type".equals(header)) {
return contentType;
}
return httpResponse.getHeader(header);
}
public T getResult() {
return result;
}
}

View File

@@ -104,21 +104,21 @@ public class RestApi {
} }
} }
private static class HttpCallback<T extends JavaScriptObject> private static class HttpImpl<T extends JavaScriptObject>
implements RequestCallback { implements RequestCallback {
private final boolean background; private final boolean background;
private final AsyncCallback<T> cb; private final HttpCallback<T> cb;
HttpCallback(boolean bg, AsyncCallback<T> cb) { HttpImpl(boolean bg, HttpCallback<T> cb) {
this.background = bg; this.background = bg;
this.cb = cb; this.cb = cb;
} }
@Override @Override
public void onResponseReceived(Request req, Response res) { public void onResponseReceived(Request req, final Response res) {
int status = res.getStatusCode(); int status = res.getStatusCode();
if (status == Response.SC_NO_CONTENT) { if (status == Response.SC_NO_CONTENT) {
cb.onSuccess(null); cb.onSuccess(new HttpResponse<T>(res, null, null));
if (!background) { if (!background) {
RpcStatus.INSTANCE.onRpcComplete(); RpcStatus.INSTANCE.onRpcComplete();
} }
@@ -126,12 +126,12 @@ public class RestApi {
} else if (200 <= status && status < 300) { } else if (200 <= status && status < 300) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
final T data; final T data;
if (isTextBody(res)) { final String type;
data = NativeString.wrap(res.getText()).cast(); if (isJsonBody(res)) {
} else if (isJsonBody(res)) {
try { try {
// javac generics bug // javac generics bug
data = RestApi.<T> cast(parseJson(res)); data = RestApi.<T> cast(parseJson(res));
type = JSON_TYPE;
} catch (JSONException e) { } catch (JSONException e) {
if (!background) { if (!background) {
RpcStatus.INSTANCE.onRpcComplete(); RpcStatus.INSTANCE.onRpcComplete();
@@ -140,6 +140,12 @@ public class RestApi {
"Invalid JSON: " + e.getMessage())); "Invalid JSON: " + e.getMessage()));
return; return;
} }
} else if (isEncodedBase64(res)) {
data = NativeString.wrap(decodeBase64(res.getText())).cast();
type = simpleType(res.getHeader("X-FYI-Content-Type"));
} else if (isTextBody(res)) {
data = NativeString.wrap(res.getText()).cast();
type = TEXT_TYPE;
} else { } else {
if (!background) { if (!background) {
RpcStatus.INSTANCE.onRpcComplete(); RpcStatus.INSTANCE.onRpcComplete();
@@ -154,7 +160,7 @@ public class RestApi {
@Override @Override
public void execute() { public void execute() {
try { try {
cb.onSuccess(data); cb.onSuccess(new HttpResponse<>(res, type, data));
} finally { } finally {
if (!background) { if (!background) {
RpcStatus.INSTANCE.onRpcComplete(); RpcStatus.INSTANCE.onRpcComplete();
@@ -318,21 +324,24 @@ public class RestApi {
} }
public <T extends JavaScriptObject> void get(AsyncCallback<T> cb) { public <T extends JavaScriptObject> void get(AsyncCallback<T> cb) {
get(wrap(cb));
}
public <T extends JavaScriptObject> void get(HttpCallback<T> cb) {
send(GET, cb); send(GET, cb);
} }
public <T extends JavaScriptObject> void delete(AsyncCallback<T> cb) { public <T extends JavaScriptObject> void delete(AsyncCallback<T> cb) {
delete(wrap(cb));
}
public <T extends JavaScriptObject> void delete(HttpCallback<T> cb) {
send(DELETE, cb); send(DELETE, cb);
} }
public <T extends JavaScriptObject> void delete(JavaScriptObject content, private <T extends JavaScriptObject> void send(Method method,
AsyncCallback<T> cb) { HttpCallback<T> cb) {
sendJSON(DELETE, content, cb); HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
}
private <T extends JavaScriptObject> void send(
Method method, AsyncCallback<T> cb) {
HttpCallback<T> httpCallback = new HttpCallback<>(background, cb);
try { try {
if (!background) { if (!background) {
RpcStatus.INSTANCE.onRpcStart(); RpcStatus.INSTANCE.onRpcStart();
@@ -346,33 +355,59 @@ public class RestApi {
public <T extends JavaScriptObject> void post( public <T extends JavaScriptObject> void post(
JavaScriptObject content, JavaScriptObject content,
AsyncCallback<T> cb) { AsyncCallback<T> cb) {
post(content, wrap(cb));
}
public <T extends JavaScriptObject> void post(
JavaScriptObject content,
HttpCallback<T> cb) {
sendJSON(POST, content, cb); sendJSON(POST, content, cb);
} }
public <T extends JavaScriptObject> void post(String content, public <T extends JavaScriptObject> void post(String content,
AsyncCallback<T> cb) { AsyncCallback<T> cb) {
post(content, wrap(cb));
}
public <T extends JavaScriptObject> void post(String content,
HttpCallback<T> cb) {
sendRaw(POST, content, cb); sendRaw(POST, content, cb);
} }
public <T extends JavaScriptObject> void put(AsyncCallback<T> cb) { public <T extends JavaScriptObject> void put(AsyncCallback<T> cb) {
put(wrap(cb));
}
public <T extends JavaScriptObject> void put(HttpCallback<T> cb) {
send(PUT, cb); send(PUT, cb);
} }
public <T extends JavaScriptObject> void put(String content, public <T extends JavaScriptObject> void put(String content,
AsyncCallback<T> cb) { AsyncCallback<T> cb) {
put(content, wrap(cb));
}
public <T extends JavaScriptObject> void put(String content,
HttpCallback<T> cb) {
sendRaw(PUT, content, cb); sendRaw(PUT, content, cb);
} }
public <T extends JavaScriptObject> void put( public <T extends JavaScriptObject> void put(
JavaScriptObject content, JavaScriptObject content,
AsyncCallback<T> cb) { AsyncCallback<T> cb) {
put(content, wrap(cb));
}
public <T extends JavaScriptObject> void put(
JavaScriptObject content,
HttpCallback<T> cb) {
sendJSON(PUT, content, cb); sendJSON(PUT, content, cb);
} }
private <T extends JavaScriptObject> void sendJSON( private <T extends JavaScriptObject> void sendJSON(
Method method, JavaScriptObject content, Method method, JavaScriptObject content,
AsyncCallback<T> cb) { HttpCallback<T> cb) {
HttpCallback<T> httpCallback = new HttpCallback<>(background, cb); HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
try { try {
if (!background) { if (!background) {
RpcStatus.INSTANCE.onRpcStart(); RpcStatus.INSTANCE.onRpcStart();
@@ -385,11 +420,15 @@ public class RestApi {
} }
} }
private static native String str(JavaScriptObject jso) /*-{ return JSON.stringify(jso); }-*/; private static native String str(JavaScriptObject jso)
/*-{ return JSON.stringify(jso) }-*/;
private static native String decodeBase64(String a)
/*-{ return $wnd.atob(a) }-*/;
private <T extends JavaScriptObject> void sendRaw(Method method, String body, private <T extends JavaScriptObject> void sendRaw(Method method, String body,
AsyncCallback<T> cb) { HttpCallback<T> cb) {
HttpCallback<T> httpCallback = new HttpCallback<>(background, cb); HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
try { try {
if (!background) { if (!background) {
RpcStatus.INSTANCE.onRpcStart(); RpcStatus.INSTANCE.onRpcStart();
@@ -422,16 +461,22 @@ public class RestApi {
return isContentType(res, TEXT_TYPE); return isContentType(res, TEXT_TYPE);
} }
private static boolean isEncodedBase64(Response res) {
return "base64".equals(res.getHeader("X-FYI-Content-Encoding"))
&& isTextBody(res);
}
private static boolean isContentType(Response res, String want) { private static boolean isContentType(Response res, String want) {
String type = res.getHeader("Content-Type"); String type = res.getHeader("Content-Type");
if (type == null) { return type != null && want.equals(simpleType(type));
return false;
} }
private static String simpleType(String type) {
int semi = type.indexOf(';'); int semi = type.indexOf(';');
if (semi >= 0) { if (semi >= 0) {
type = type.substring(0, semi).trim(); return type.substring(0, semi).trim();
} }
return want.equals(type); return type;
} }
private static JSONValue parseJson(Response res) private static JSONValue parseJson(Response res)
@@ -464,4 +509,19 @@ public class RestApi {
throw new JSONException("unsupported JSON type"); throw new JSONException("unsupported JSON type");
} }
} }
private static <T extends JavaScriptObject> HttpCallback<T> wrap(
final AsyncCallback<T> cb) {
return new HttpCallback<T>() {
@Override
public void onSuccess(HttpResponse<T> r) {
cb.onSuccess(r.getResult());
}
@Override
public void onFailure(Throwable e) {
cb.onFailure(e);
}
};
}
} }

View File

@@ -52,6 +52,7 @@ import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.ObjectId;
import org.kohsuke.args4j.Option; import org.kohsuke.args4j.Option;
import java.io.IOException; import java.io.IOException;
@@ -433,8 +434,8 @@ public class ChangeEdits implements
throws ResourceNotFoundException, IOException { throws ResourceNotFoundException, IOException {
try { try {
return Response.ok(fileContentUtil.getContent( return Response.ok(fileContentUtil.getContent(
rsrc.getChangeEdit().getChange().getProject(), rsrc.getControl().getProjectControl().getProjectState(),
rsrc.getChangeEdit().getRevision().get(), ObjectId.fromString(rsrc.getChangeEdit().getRevision().get()),
rsrc.getPath())); rsrc.getPath()));
} catch (ResourceNotFoundException rnfe) { } catch (ResourceNotFoundException rnfe) {
return Response.none(); return Response.none();
@@ -502,8 +503,10 @@ public class ChangeEdits implements
IOException, ResourceNotFoundException { IOException, ResourceNotFoundException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange()); Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
if (edit.isPresent()) { if (edit.isPresent()) {
return BinaryResult.create( String msg = edit.get().getEditCommit().getFullMessage();
edit.get().getEditCommit().getFullMessage()).base64(); return BinaryResult.create(msg)
.setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
.base64();
} }
throw new ResourceNotFoundException(); throw new ResourceNotFoundException();
} }

View File

@@ -20,7 +20,6 @@ import com.google.gerrit.common.data.PatchScript.FileMode;
import com.google.gerrit.extensions.restapi.BinaryResult; import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.FileTypeRegistry; import com.google.gerrit.server.FileTypeRegistry;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
@@ -28,6 +27,7 @@ import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
@@ -44,6 +44,7 @@ public class FileContentUtil {
public static final String TEXT_X_GERRIT_COMMIT_MESSAGE = "text/x-gerrit-commit-message"; public static final String TEXT_X_GERRIT_COMMIT_MESSAGE = "text/x-gerrit-commit-message";
private static final String X_GIT_SYMLINK = "x-git/symlink"; private static final String X_GIT_SYMLINK = "x-git/symlink";
private static final String X_GIT_GITLINK = "x-git/gitlink"; private static final String X_GIT_GITLINK = "x-git/gitlink";
private static final int MAX_SIZE = 5 << 20;
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final FileTypeRegistry registry; private final FileTypeRegistry registry;
@@ -55,28 +56,50 @@ public class FileContentUtil {
this.registry = ftr; this.registry = ftr;
} }
public BinaryResult getContent(Project.NameKey project, String revstr, public BinaryResult getContent(ProjectState project, ObjectId revstr,
String path) throws ResourceNotFoundException, IOException { String path) throws ResourceNotFoundException, IOException {
Repository repo = repoManager.openRepository(project); Repository repo = openRepository(project);
try { try {
RevWalk rw = new RevWalk(repo); RevWalk rw = new RevWalk(repo);
try { try {
RevCommit commit = rw.parseCommit(repo.resolve(revstr)); RevCommit commit = rw.parseCommit(revstr);
TreeWalk tw = ObjectReader reader = rw.getObjectReader();
TreeWalk.forPath(rw.getObjectReader(), path, TreeWalk tw = TreeWalk.forPath(reader, path, commit.getTree());
commit.getTree().getId());
if (tw == null) { if (tw == null) {
throw new ResourceNotFoundException(); throw new ResourceNotFoundException();
} }
final ObjectLoader object = repo.open(tw.getObjectId(0));
@SuppressWarnings("resource") org.eclipse.jgit.lib.FileMode mode = tw.getFileMode(0);
BinaryResult result = new BinaryResult() { ObjectId id = tw.getObjectId(0);
@Override if (mode == org.eclipse.jgit.lib.FileMode.GITLINK) {
public void writeTo(OutputStream os) throws IOException { return BinaryResult.create(id.name())
object.copyTo(os); .setContentType(X_GIT_GITLINK)
.base64();
} }
};
return result.setContentLength(object.getSize()).base64(); final ObjectLoader obj = repo.open(id, OBJ_BLOB);
byte[] raw;
try {
raw = obj.getCachedBytes(MAX_SIZE);
} catch (LargeObjectException e) {
raw = null;
}
BinaryResult result;
if (raw != null) {
result = BinaryResult.create(raw);
} else {
result = asBinaryResult(obj);
}
String type;
if (mode == org.eclipse.jgit.lib.FileMode.SYMLINK) {
type = X_GIT_SYMLINK;
} else {
type = registry.getMimeType(path, raw).toString();
type = resolveContentType(project, path, FileMode.FILE, type);
}
return result.setContentType(type).base64();
} finally { } finally {
rw.release(); rw.release();
} }
@@ -85,10 +108,20 @@ public class FileContentUtil {
} }
} }
private static BinaryResult asBinaryResult(final ObjectLoader obj) {
@SuppressWarnings("resource")
BinaryResult result = new BinaryResult() {
@Override
public void writeTo(OutputStream os) throws IOException {
obj.copyTo(os);
}
}.setContentLength(obj.getSize());
return result;
}
public String getContentType(ProjectState project, ObjectId rev, public String getContentType(ProjectState project, ObjectId rev,
String path) throws ResourceNotFoundException, IOException { String path) throws ResourceNotFoundException, IOException {
Repository repo = Repository repo = openRepository(project);
repoManager.openRepository(project.getProject().getNameKey());
try { try {
RevWalk rw = new RevWalk(repo); RevWalk rw = new RevWalk(repo);
try { try {
@@ -109,7 +142,7 @@ public class FileContentUtil {
ObjectLoader blob = reader.open(tw.getObjectId(0), OBJ_BLOB); ObjectLoader blob = reader.open(tw.getObjectId(0), OBJ_BLOB);
byte[] raw; byte[] raw;
try { try {
raw = blob.getCachedBytes(5 << 20); raw = blob.getCachedBytes(MAX_SIZE);
} catch (LargeObjectException e) { } catch (LargeObjectException e) {
raw = null; raw = null;
} }
@@ -147,4 +180,9 @@ public class FileContentUtil {
throw new IllegalStateException("file mode: " + fileMode); throw new IllegalStateException("file mode: " + fileMode);
} }
} }
private Repository openRepository(ProjectState project)
throws RepositoryNotFoundException, IOException {
return repoManager.openRepository(project.getProject().getNameKey());
}
} }

View File

@@ -24,6 +24,8 @@ import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.eclipse.jgit.lib.ObjectId;
import java.io.IOException; import java.io.IOException;
@Singleton @Singleton
@@ -44,12 +46,14 @@ public class GetContent implements RestReadView<FileResource> {
OrmException { OrmException {
String path = rsrc.getPatchKey().get(); String path = rsrc.getPatchKey().get();
if (Patch.COMMIT_MSG.equals(path)) { if (Patch.COMMIT_MSG.equals(path)) {
return BinaryResult.create( String msg = changeUtil.getMessage(rsrc.getRevision().getChange());
changeUtil.getMessage(rsrc.getRevision().getChange())).base64(); return BinaryResult.create(msg)
.setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
.base64();
} }
return fileContentUtil.getContent( return fileContentUtil.getContent(
rsrc.getRevision().getControl().getProject().getNameKey(), rsrc.getRevision().getControl().getProjectControl().getProjectState(),
rsrc.getRevision().getPatchSet().getRevision().get(), ObjectId.fromString(rsrc.getRevision().getPatchSet().getRevision().get()),
path); path);
} }
} }

View File

@@ -41,4 +41,8 @@ public class BranchResource extends ProjectResource {
public String getRef() { public String getRef() {
return branchInfo.ref; return branchInfo.ref;
} }
public String getRevision() {
return branchInfo.revision;
}
} }

View File

@@ -16,7 +16,6 @@ package com.google.gerrit.server.project;
import com.google.gerrit.extensions.restapi.RestResource; import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
@@ -33,8 +32,8 @@ public class CommitResource implements RestResource {
this.commit = commit; this.commit = commit;
} }
public Project.NameKey getProject() { public ProjectControl getProject() {
return project.getNameKey(); return project.getControl();
} }
public RevCommit getCommit() { public RevCommit getCommit() {

View File

@@ -16,28 +16,29 @@ package com.google.gerrit.server.project;
import com.google.gerrit.extensions.restapi.RestResource; import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import org.eclipse.jgit.lib.ObjectId;
public class FileResource implements RestResource { public class FileResource implements RestResource {
public static final TypeLiteral<RestView<FileResource>> FILE_KIND = public static final TypeLiteral<RestView<FileResource>> FILE_KIND =
new TypeLiteral<RestView<FileResource>>() {}; new TypeLiteral<RestView<FileResource>>() {};
private final Project.NameKey project; private final ProjectControl project;
private final String rev; private final ObjectId rev;
private final String path; private final String path;
public FileResource(Project.NameKey project, String rev, String path) { public FileResource(ProjectControl project, ObjectId rev, String path) {
this.project = project; this.project = project;
this.rev = rev; this.rev = rev;
this.path = path; this.path = path;
} }
public Project.NameKey getProject() { public ProjectControl getProject() {
return project; return project;
} }
public String getRev() { public ObjectId getRev() {
return rev; return rev;
} }

View File

@@ -22,6 +22,8 @@ import com.google.gerrit.extensions.restapi.RestView;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.eclipse.jgit.lib.ObjectId;
@Singleton @Singleton
public class FilesCollection implements public class FilesCollection implements
ChildCollection<BranchResource, FileResource> { ChildCollection<BranchResource, FileResource> {
@@ -39,7 +41,10 @@ public class FilesCollection implements
@Override @Override
public FileResource parse(BranchResource parent, IdString id) { public FileResource parse(BranchResource parent, IdString id) {
return new FileResource(parent.getNameKey(), parent.getRef(), id.get()); return new FileResource(
parent.getControl(),
ObjectId.fromString(parent.getRevision()),
id.get());
} }
@Override @Override

View File

@@ -40,8 +40,7 @@ public class FilesInCommitCollection implements
@Override @Override
public FileResource parse(CommitResource parent, IdString id) public FileResource parse(CommitResource parent, IdString id)
throws ResourceNotFoundException { throws ResourceNotFoundException {
return new FileResource(parent.getProject(), parent.getCommit().getName(), return new FileResource(parent.getProject(), parent.getCommit(), id.get());
id.get());
} }
@Override @Override

View File

@@ -35,7 +35,9 @@ public class GetContent implements RestReadView<FileResource> {
@Override @Override
public BinaryResult apply(FileResource rsrc) public BinaryResult apply(FileResource rsrc)
throws ResourceNotFoundException, IOException { throws ResourceNotFoundException, IOException {
return fileContentUtil.getContent(rsrc.getProject(), rsrc.getRev(), return fileContentUtil.getContent(
rsrc.getProject().getProjectState(),
rsrc.getRev(),
rsrc.getPath()); rsrc.getPath());
} }
} }