// 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.change; import com.google.gerrit.client.changes.CommentApi; import com.google.gerrit.client.changes.CommentInfo; import com.google.gerrit.client.diff.CommentRange; import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.extensions.client.Side; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gwt.storage.client.Storage; import com.google.gwt.user.client.Cookies; import java.util.ArrayList; import java.util.Collection; public class LocalComments { private final Change.Id changeId; private final PatchSet.Id psId; private final StorageBackend storage; private static class InlineComment { final PatchSet.Id psId; final CommentInfo commentInfo; InlineComment(PatchSet.Id psId, CommentInfo commentInfo) { this.psId = psId; this.commentInfo = commentInfo; } } private static class StorageBackend { private final Storage storageBackend; StorageBackend() { storageBackend = (Storage.isLocalStorageSupported()) ? Storage.getLocalStorageIfSupported() : Storage.getSessionStorageIfSupported(); } String getItem(String key) { if (storageBackend == null) { return Cookies.getCookie(key); } return storageBackend.getItem(key); } void setItem(String key, String value) { if (storageBackend == null) { Cookies.setCookie(key, value); return; } storageBackend.setItem(key, value); } void removeItem(String key) { if (storageBackend == null) { Cookies.removeCookie(key); return; } storageBackend.removeItem(key); } Collection<String> getKeys() { if (storageBackend == null) { return Cookies.getCookieNames(); } ArrayList<String> result = new ArrayList<>(storageBackend.getLength()); for (int i = 0; i < storageBackend.getLength(); i++) { result.add(storageBackend.key(i)); } return result; } } public LocalComments(Change.Id changeId) { this.changeId = changeId; this.psId = null; this.storage = new StorageBackend(); } public LocalComments(PatchSet.Id psId) { this.changeId = psId.getParentKey(); this.psId = psId; this.storage = new StorageBackend(); } public String getReplyComment() { String comment = storage.getItem(getReplyCommentName()); storage.removeItem(getReplyCommentName()); return comment; } public void setReplyComment(String comment) { storage.setItem(getReplyCommentName(), comment.trim()); } public boolean hasReplyComment() { return storage.getKeys().contains(getReplyCommentName()); } public void removeReplyComment() { if (hasReplyComment()) { storage.removeItem(getReplyCommentName()); } } private String getReplyCommentName() { return "savedReplyComment-" + changeId.toString(); } public static void saveInlineComments() { final StorageBackend storage = new StorageBackend(); for (final String cookie : storage.getKeys()) { if (isInlineComment(cookie)) { InlineComment input = getInlineComment(cookie); if (input.commentInfo.id() == null) { CommentApi.createDraft( input.psId, input.commentInfo, new GerritCallback<CommentInfo>() { @Override public void onSuccess(CommentInfo result) { storage.removeItem(cookie); } }); } else { CommentApi.updateDraft( input.psId, input.commentInfo.id(), input.commentInfo, new GerritCallback<CommentInfo>() { @Override public void onSuccess(CommentInfo result) { storage.removeItem(cookie); } @Override public void onFailure(Throwable caught) { if (RestApi.isNotFound(caught)) { // the draft comment, that was supposed to be updated, // was deleted in the meantime storage.removeItem(cookie); } else { super.onFailure(caught); } } }); } } } } public void setInlineComment(CommentInfo comment) { String name = getInlineCommentName(comment); if (name == null) { // Failed to get the store key -- so we can't continue. return; } storage.setItem(name, comment.message().trim()); } public boolean hasInlineComments() { for (String cookie : storage.getKeys()) { if (isInlineComment(cookie)) { return true; } } return false; } private static boolean isInlineComment(String key) { return key.startsWith("patchCommentEdit-") || key.startsWith("patchReply-") || key.startsWith("patchComment-"); } private static InlineComment getInlineComment(String key) { String path; Side side = Side.PARENT; int line = 0; CommentRange range; StorageBackend storage = new StorageBackend(); String[] elements = key.split("-"); int offset = 1; if (key.startsWith("patchReply-") || key.startsWith("patchCommentEdit-")) { offset = 2; } Change.Id changeId = new Change.Id(Integer.parseInt(elements[offset + 0])); PatchSet.Id psId = new PatchSet.Id(changeId, Integer.parseInt(elements[offset + 1])); path = atob(elements[offset + 2]); side = (Side.PARENT.toString().equals(elements[offset + 3])) ? Side.PARENT : Side.REVISION; range = null; if (elements[offset + 4].startsWith("R")) { String rangeStart = elements[offset + 4].substring(1); String rangeEnd = elements[offset + 5]; String[] split = rangeStart.split(","); int sl = Integer.parseInt(split[0]); int sc = Integer.parseInt(split[1]); split = rangeEnd.split(","); int el = Integer.parseInt(split[0]); int ec = Integer.parseInt(split[1]); range = CommentRange.create(sl, sc, el, ec); line = sl; } else { line = Integer.parseInt(elements[offset + 4]); } CommentInfo info = CommentInfo.create(path, side, line, range, false); info.message(storage.getItem(key)); if (key.startsWith("patchReply-")) { info.inReplyTo(elements[1]); } else if (key.startsWith("patchCommentEdit-")) { info.id(elements[1]); } InlineComment inlineComment = new InlineComment(psId, info); return inlineComment; } private String getInlineCommentName(CommentInfo comment) { if (psId == null) { return null; } String result = "patchComment-"; if (comment.id() != null) { result = "patchCommentEdit-" + comment.id() + "-"; } else if (comment.inReplyTo() != null) { result = "patchReply-" + comment.inReplyTo() + "-"; } result += changeId + "-" + psId.getId() + "-" + btoa(comment.path()) + "-" + comment.side() + "-"; if (comment.hasRange()) { result += "R" + comment.range().startLine() + "," + comment.range().startCharacter() + "-" + comment.range().endLine() + "," + comment.range().endCharacter(); } else { result += comment.line(); } return result; } private static native String btoa(String a) /*-{ return btoa(a); }-*/; private static native String atob(String b) /*-{ return atob(b); }-*/; }