// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.collide.client.collaboration;
import com.google.collide.client.bootstrap.BootstrapSession;
import com.google.collide.client.collaboration.FileConcurrencyController.DocOpListener;
import com.google.collide.client.collaboration.cc.GenericOperationChannel.SendOpService;
import com.google.collide.client.communication.FrontendApi;
import com.google.collide.client.communication.FrontendApi.ApiCallback;
import com.google.collide.dto.DocOp;
import com.google.collide.dto.ServerError.FailureReason;
import com.google.collide.dto.ServerToClientDocOps;
import com.google.collide.dto.client.DtoClientImpls.ClientToServerDocOpImpl;
import com.google.collide.dto.client.DtoClientImpls.DocOpImpl;
import com.google.collide.dto.client.DtoClientImpls.ServerToClientDocOpImpl;
import com.google.collide.json.client.Jso;
import com.google.collide.json.client.JsoArray;
import com.google.collide.shared.util.ListenerManager;
import com.google.collide.shared.util.ListenerManager.Dispatcher;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import java.util.List;
/**
* Helper to take outgoing doc ops from the local concurrency control library
* and send them to the server.
*
*/
class DocOpSender implements SendOpService<DocOp>, LastClientToServerDocOpProvider {
private final ListenerManager<DocOpListener> docOpListenerManager;
private final int documentId;
private final String fileEditSessionKey;
private final FrontendApi frontendApi;
private final IncomingDocOpDemultiplexer docOpDemux;
private ClientToServerDocOpCreationParticipant clientToServerDocOpCreationParticipant;
private ClientToServerDocOpImpl lastClientToServerDocOpMsg;
private final DocOpRecoveryInitiator docOpRecoveryInitiator;
public DocOpSender(FrontendApi frontendApi,
IncomingDocOpDemultiplexer docOpDemux,
String fileEditSessionKey,
int documentId,
ListenerManager<DocOpListener> docOpListenerManager,
DocOpRecoveryInitiator docOpRecoveryInitiator) {
this.frontendApi = frontendApi;
this.docOpDemux = docOpDemux;
this.fileEditSessionKey = fileEditSessionKey;
this.documentId = documentId;
this.docOpListenerManager = docOpListenerManager;
this.docOpRecoveryInitiator = docOpRecoveryInitiator;
}
@Override
public void callbackNotNeeded(SendOpService.Callback callback) {
}
@Override
public void requestRevision(SendOpService.Callback callback) {
/*
* TODO: get revision from server, but for now this is never
* called since we are not handling connection errors fully
*/
assert false;
}
@Override
public void submitOperations(
int revision, final List<DocOp> operations, final SendOpService.Callback callback) {
try {
/*
* Copy the operations into the list.
* TODO: Consider making the client code maintain this list as a native collection.
*/
JsoArray<String> docOps = JsoArray.create();
for (int i = 0, n = operations.size(); i < n; i++) {
docOps.add(Jso.serialize((DocOpImpl) operations.get(i)));
}
ClientToServerDocOpImpl message = ClientToServerDocOpImpl
.make()
.setFileEditSessionKey(fileEditSessionKey)
.setCcRevision(revision)
.setClientId(BootstrapSession.getBootstrapSession().getActiveClientId())
.setDocOps2(docOps);
if (clientToServerDocOpCreationParticipant != null) {
clientToServerDocOpCreationParticipant.onCreateClientToServerDocOp(message);
}
frontendApi.MUTATE_FILE.send(message, new ApiCallback<ServerToClientDocOps>() {
@Override
public void onFail(FailureReason reason) {
if (reason == FailureReason.MISSING_WORKSPACE_SESSION) {
docOpRecoveryInitiator.teardown();
} else {
docOpRecoveryInitiator.recover();
}
}
@Override
public void onMessageReceived(ServerToClientDocOps message) {
for (int i = 0; i < message.getDocOps().size(); i++) {
docOpDemux.handleServerToClientDocOpMsg(
(ServerToClientDocOpImpl) message.getDocOps().get(i));
}
}
});
lastClientToServerDocOpMsg = message;
docOpListenerManager.dispatch(new Dispatcher<DocOpListener>() {
@Override
public void dispatch(DocOpListener listener) {
listener.onDocOpSent(documentId, operations);
}
});
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
/*
* Need to defer this since the client is not expecting a success
* reply from within the same call stack
*/
/*
* TODO: Need the applied revision. We can't easily get
* it since unlike HTTP, our push channel does not have a mechanism
* for an inline response (could create a separate response message,
* which is this TODO). We'll be okay for now without it because the
* cc lib uses it for recovery, but we never report failures so it
* doesn't have exercise the recovery logic right now. It also uses
* this for optimizations if the given applied revision matches what
* it expects, but since we give an unexpected value, it does nothing
* with it.
*/
callback.onSuccess(Integer.MIN_VALUE);
}
});
} catch (Throwable t) {
callback.onFatalError(t);
}
}
void setDocOpCreationParticipant(ClientToServerDocOpCreationParticipant participant) {
clientToServerDocOpCreationParticipant = participant;
}
@Override
public ClientToServerDocOpImpl getLastClientToServerDocOpMsg() {
return lastClientToServerDocOpMsg;
}
/**
* Clears the message that would be returned by
* {@link #getLastClientToServerDocOpMsg()}.
*
* @param clientToServerDocOpMsgToDelete if provided, the current message must
* match the given message for it to be cleared
*/
@Override
public void clearLastClientToServerDocOpMsg(
ClientToServerDocOpImpl clientToServerDocOpMsgToDelete) {
if (clientToServerDocOpMsgToDelete == null
|| clientToServerDocOpMsgToDelete == lastClientToServerDocOpMsg) {
lastClientToServerDocOpMsg = null;
}
}
}