// 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.document; import com.google.collide.client.AppContext; import com.google.collide.client.communication.FrontendApi.ApiCallback; import com.google.collide.client.document.DocumentManager.GetDocumentCallback; import com.google.collide.client.status.StatusMessage; import com.google.collide.client.status.StatusMessage.MessageType; import com.google.collide.client.util.PathUtil; import com.google.collide.client.util.logging.Log; import com.google.collide.dto.FileContents.ContentType; import com.google.collide.dto.GetFileContentsResponse; import com.google.collide.dto.RoutingTypes; import com.google.collide.dto.ServerError.FailureReason; import com.google.collide.dto.client.DtoClientImpls.GetFileContentsImpl; import com.google.collide.json.shared.JsonArray; import com.google.collide.json.shared.JsonStringMap; import com.google.collide.shared.util.JsonCollections; import com.google.common.base.Preconditions; /** * Controller responsible for loading documents (and uneditable files) from the * server. * * This class accepts multiple calls to * {@link #load(PathUtil, GetDocumentCallback)} for the same path and intelligently * batches together the callbacks so only one network request will occur. */ class DocumentManagerNetworkController { private final DocumentManager documentManager; private final AppContext appContext; /** Path to a list of {@link GetDocumentCallback} */ JsonStringMap<JsonArray<GetDocumentCallback>> outstandingCallbacks = JsonCollections.createMap(); private StatusMessage loadingMessage; DocumentManagerNetworkController(DocumentManager documentManager, AppContext appContext) { this.documentManager = documentManager; this.appContext = appContext; } public void teardown() { if (loadingMessage != null) { cancelLoadingMessage(); } appContext.getMessageFilter().removeMessageRecipient(RoutingTypes.GETFILECONTENTSRESPONSE); } void load(PathUtil path, GetDocumentCallback callback) { boolean shouldRequestFile = addCallback(path, callback); if (shouldRequestFile) { requestFile(path); } } private boolean addCallback(PathUtil path, GetDocumentCallback callback) { JsonArray<GetDocumentCallback> callbacks = outstandingCallbacks.get(path.getPathString()); if (callbacks == null) { callbacks = JsonCollections.createArray(callback); outstandingCallbacks.put(path.getPathString(), callbacks); return true; } else { callbacks.add(callback); return false; } } private void requestFile(final PathUtil path) { delayLoadingMessage(path); // Fetch the file's contents GetFileContentsImpl getFileContents = GetFileContentsImpl.make().setPath(path.getPathString()); appContext.getFrontendApi().GET_FILE_CONTENTS.send(getFileContents, new ApiCallback<GetFileContentsResponse>() { @Override public void onMessageReceived(GetFileContentsResponse response) { handleFileReceived(response); } @Override public void onFail(FailureReason reason) { Log.error(getClass(), "Failed to retrieve file contents for path " + path); } }); } /** * Called when the file contents are received from the network. Routes to the appropriate content * handling mechanism depending on whether or not the file content type is text, an image, or some * other binary file. */ private void handleFileReceived(GetFileContentsResponse response) { boolean isUneditable = (response.getFileContents().getContentType() == ContentType.UNKNOWN_BINARY) || (response.getFileContents().getContentType() == ContentType.IMAGE); PathUtil path = new PathUtil(response.getFileContents().getPath()); cancelLoadingMessage(); JsonArray<GetDocumentCallback> callbacks = outstandingCallbacks.remove(path.getPathString()); Preconditions.checkNotNull(callbacks); if (!response.getFileExists()) { // Dispatch to callbacks directly for (int i = 0, n = callbacks.size(); i < n; i++) { callbacks.get(i).onFileNotFoundReceived(); } } else if (isUneditable) { // Dispatch to callbacks directly for (int i = 0, n = callbacks.size(); i < n; i++) { callbacks.get(i).onUneditableFileContentsReceived(response.getFileContents()); } } else { documentManager.handleEditableFileReceived(response.getFileContents(), callbacks); } } private void delayLoadingMessage(PathUtil path) { cancelLoadingMessage(); loadingMessage = new StatusMessage(appContext.getStatusManager(), MessageType.LOADING, "Loading " + path.getBaseName() + "..."); loadingMessage.fireDelayed(StatusMessage.DEFAULT_DELAY); } private void cancelLoadingMessage() { if (loadingMessage != null) { loadingMessage.cancel(); loadingMessage = null; } } }