/*
* ProfilerEditingTarget.java
*
* Copyright (C) 2009-12 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.studio.client.workbench.views.source.editors.profiler;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.rstudio.core.client.CodeNavigationTarget;
import org.rstudio.core.client.CommandWithArg;
import org.rstudio.core.client.Debug;
import org.rstudio.core.client.FilePosition;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.command.AppCommand;
import org.rstudio.core.client.command.CommandBinder;
import org.rstudio.core.client.command.Handler;
import org.rstudio.core.client.events.EnsureHeightHandler;
import org.rstudio.core.client.events.EnsureVisibleHandler;
import org.rstudio.core.client.events.HasSelectionCommitHandlers;
import org.rstudio.core.client.events.SelectionCommitHandler;
import org.rstudio.core.client.files.FileSystemContext;
import org.rstudio.core.client.files.FileSystemItem;
import org.rstudio.core.client.resources.ImageResource2x;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.core.client.widget.OperationWithInput;
import org.rstudio.core.client.widget.ProgressIndicator;
import org.rstudio.core.client.widget.ProgressOperationWithInput;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.FileDialogs;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.ReadOnlyValue;
import org.rstudio.studio.client.common.SimpleRequestCallback;
import org.rstudio.studio.client.common.Value;
import org.rstudio.studio.client.common.filetypes.FileIconResources;
import org.rstudio.studio.client.common.filetypes.FileType;
import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
import org.rstudio.studio.client.common.filetypes.ProfilerType;
import org.rstudio.studio.client.common.filetypes.TextFileType;
import org.rstudio.studio.client.rsconnect.model.PublishHtmlSource;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.server.Void;
import org.rstudio.studio.client.workbench.WorkbenchContext;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
import org.rstudio.studio.client.workbench.views.source.SourceWindowManager;
import org.rstudio.studio.client.workbench.views.source.editors.EditingTarget;
import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfileOperationResponse;
import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfilerContents;
import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfilerServerOperations;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
import org.rstudio.studio.client.workbench.views.source.events.CollabEditStartParams;
import org.rstudio.studio.client.workbench.views.source.events.DocWindowChangedEvent;
import org.rstudio.studio.client.workbench.views.source.events.PopoutDocEvent;
import org.rstudio.studio.client.workbench.views.source.events.SourceNavigationEvent;
import org.rstudio.studio.client.workbench.views.source.model.DocTabDragParams;
import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
import org.rstudio.studio.client.workbench.views.source.model.SourceNavigation;
import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
import java.util.HashMap;
import java.util.HashSet;
public class ProfilerEditingTarget implements EditingTarget,
HasSelectionCommitHandlers<CodeNavigationTarget>
{
interface MyCommandBinder
extends CommandBinder<Commands, ProfilerEditingTarget>
{
}
private static final MyCommandBinder commandBinder =
GWT.create(MyCommandBinder.class);
@Inject
public ProfilerEditingTarget(ProfilerPresenter presenter,
Commands commands,
EventBus events,
ProfilerServerOperations server,
GlobalDisplay globalDisplay,
Provider<SourceWindowManager> pSourceWindowManager,
FileDialogs fileDialogs,
RemoteFileSystemContext fileContext,
WorkbenchContext workbenchContext,
EventBus eventBus,
SourceServerOperations sourceServer,
FileTypeRegistry fileTypeRegistry)
{
presenter_ = presenter;
commands_ = commands;
events_ = events;
server_ = server;
globalDisplay_ = globalDisplay;
pSourceWindowManager_ = pSourceWindowManager;
fileDialogs_ = fileDialogs;
fileContext_ = fileContext;
workbenchContext_ = workbenchContext;
eventBus_ = eventBus;
sourceServer_ = sourceServer;
fileTypeRegistry_ = fileTypeRegistry;
if (!initializedEvents_)
{
initializedEvents_ = true;
initializeEvents();
}
}
public String getId()
{
return doc_.getId();
}
@Override
public void adaptToExtendedFileType(String extendedType)
{
}
@Override
public String getExtendedFileType()
{
return null;
}
public HasValue<String> getName()
{
return name_;
}
public String getTitle()
{
return name_.getValue();
}
public String getContext()
{
return null;
}
public ImageResource getIcon()
{
return new ImageResource2x(FileIconResources.INSTANCE.iconProfiler2x());
}
@Override
public TextFileType getTextFileType()
{
return null;
}
public String getTabTooltip()
{
return "R Profiler";
}
public HashSet<AppCommand> getSupportedCommands()
{
HashSet<AppCommand> commands = fileType_.getSupportedCommands(commands_);
if (SourceWindowManager.isMainSourceWindow())
commands.add(commands_.popoutDoc());
else
commands.add(commands_.returnDocToMain());
commands.add(commands_.printSourceDoc());
return commands;
}
@Override
public void manageCommands()
{
}
@Override
public boolean canCompilePdf()
{
return false;
}
@Override
public void verifyCppPrerequisites()
{
}
@Override
public Position search(String regex)
{
return null;
}
@Override
public Position search(Position startPos, String regex)
{
return null;
}
@Override
public void forceLineHighlighting()
{
}
public void focus()
{
}
public void onActivate()
{
activeProfilerEditingTarger_ = this;
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
public void execute()
{
commands_.gotoProfileSource().setEnabled(hasValidPath_);
}
});
final Operation activateOperation = new Operation()
{
@Override
public void execute()
{
if (!htmlPathInitialized_) {
htmlPathInitialized_ = true;
htmlPath_ = getContents().getHtmlPath();
htmlLocalPath_ = getContents().getHtmlLocalPath();
isUserSaved_ = getContents().isUserSaved();
if (htmlPath_ == null)
{
presenter_.buildHtmlPath(new OperationWithInput<ProfileOperationResponse>()
{
@Override
public void execute(ProfileOperationResponse response)
{
htmlPath_ = response.getHtmlPath();
htmlLocalPath_ = response.getHtmlLocalPath();
persistDocumentProperty("htmlPath", htmlPath_);
persistDocumentProperty("htmlLocalPath", htmlLocalPath_);
view_.showProfilePage(htmlPath_);
pSourceWindowManager_.get().maximizeSourcePaneIfNecessary();
}
}, new Operation()
{
@Override
public void execute()
{
server_.clearProfile(getPath(), new ServerRequestCallback<JavaScriptObject>()
{
@Override
public void onResponseReceived(JavaScriptObject response)
{
commands_.closeSourceDoc().execute();
}
@Override
public void onError(ServerError error)
{
Debug.logError(error);
commands_.closeSourceDoc().execute();
}
});
}
}, getPath());
}
else
{
view_.showProfilePage(htmlPath_);
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
public void execute()
{
pSourceWindowManager_.get().maximizeSourcePaneIfNecessary();
}
});
}
}
}
};
if (getId() != null && !SourceWindowManager.isMainSourceWindow()) {
sourceServer_.getSourceDocument(getId(), new ServerRequestCallback<SourceDocument>()
{
@Override
public void onResponseReceived(SourceDocument document)
{
doc_ = document;
activateOperation.execute();
}
@Override
public void onError(ServerError error)
{
Debug.logError(error);
}
});
}
else {
activateOperation.execute();
}
// If we're already hooked up for some reason, unhook.
// This shouldn't happen though.
if (commandHandlerReg_ != null)
{
Debug.log("Warning: onActivate called twice without intervening onDeactivate");
commandHandlerReg_.removeHandler();
commandHandlerReg_ = null;
}
commandHandlerReg_ = commandBinder.bind(commands_, this);
}
public void onDeactivate()
{
if (activeProfilerEditingTarger_ == this)
{
activeProfilerEditingTarger_ = null;
}
recordCurrentNavigationPosition();
commandHandlerReg_.removeHandler();
commandHandlerReg_ = null;
}
@Override
public void onInitiallyLoaded()
{
}
@Override
public void recordCurrentNavigationPosition()
{
events_.fireEvent(new SourceNavigationEvent(
SourceNavigation.create(
getId(),
getPath(),
SourcePosition.create(0, 0))));
}
@Override
public void navigateToPosition(SourcePosition position,
boolean recordCurrent)
{
}
@Override
public void navigateToPosition(SourcePosition position,
boolean recordCurrent,
boolean highlightLine)
{
}
@Override
public void restorePosition(SourcePosition position)
{
}
@Override
public SourcePosition currentPosition()
{
return null;
}
@Override
public void setCursorPosition(Position position)
{
}
@Override
public void ensureCursorVisible()
{
}
@Override
public boolean isAtSourceRow(SourcePosition position)
{
// always true because profiler docs don't have the
// concept of a position
return true;
}
@Override
public void highlightDebugLocation(SourcePosition startPos,
SourcePosition endPos,
boolean executing)
{
}
@Override
public void endDebugHighlighting()
{
}
@Override
public void beginCollabSession(CollabEditStartParams params)
{
}
@Override
public void endCollabSession()
{
}
public boolean onBeforeDismiss()
{
return true;
}
public ReadOnlyValue<Boolean> dirtyState()
{
return neverDirtyState_;
}
@Override
public boolean isSaveCommandActive()
{
return !isUserSaved_;
}
@Override
public void forceSaveCommandActive()
{
}
public void save(Command onCompleted)
{
onCompleted.execute();
}
public void saveWithPrompt(Command onCompleted, Command onCancelled)
{
onCompleted.execute();
}
public void revertChanges(Command onCompleted)
{
onCompleted.execute();
}
@Handler
void onPrintSourceDoc()
{
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
public void execute()
{
view_.print();
}
});
}
private String getAndSetInitialName()
{
String name = getContents().getName();
boolean createProfile = getContents().getCreateProfile();
if (!StringUtil.isNullOrEmpty(name)) {
return name;
}
else if (createProfile) {
String defaultName = defaultNameProvider_.get();
persistDocumentProperty("name", defaultName);
return defaultName;
}
else {
String nameFromFile = FileSystemItem.getNameFromPath(getPath());
persistDocumentProperty("name", nameFromFile);
return nameFromFile;
}
}
public void initialize(SourceDocument document,
FileSystemContext fileContext,
FileType type,
Provider<String> defaultNameProvider)
{
// initialize doc, view, and presenter
doc_ = document;
PublishHtmlSource publishHtmlSource = new PublishHtmlSource() {
@Override
public void generatePublishHtml(CommandWithArg<String> onComplete)
{
onComplete.execute(htmlLocalPath_) ;
}
@Override
public String getTitle()
{
return "Profile";
}
};
view_ = new ProfilerEditingTargetWidget(commands_, publishHtmlSource);
defaultNameProvider_ = defaultNameProvider;
getName().setValue(getAndSetInitialName());
presenter_.attach(doc_, view_);
}
public void onDismiss(int dismissType)
{
presenter_.detach();
}
public long getFileSizeLimit()
{
return Long.MAX_VALUE;
}
public long getLargeFileSize()
{
return Long.MAX_VALUE;
}
public Widget asWidget()
{
return view_.asWidget();
}
public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
{
return new HandlerRegistration()
{
public void removeHandler()
{
}
};
}
public HandlerRegistration addEnsureHeightHandler(EnsureHeightHandler handler)
{
return new HandlerRegistration()
{
public void removeHandler()
{
}
};
}
@Override
public HandlerRegistration addCloseHandler(CloseHandler<java.lang.Void> handler)
{
return new HandlerRegistration()
{
public void removeHandler()
{
}
};
}
public void fireEvent(GwtEvent<?> event)
{
assert false : "Not implemented";
}
public String getPath()
{
return getContents().getPath();
}
@Override
public HandlerRegistration addSelectionCommitHandler(SelectionCommitHandler<CodeNavigationTarget> handler)
{
return null;
}
@Handler
void onSaveSourceDoc()
{
saveNewFile(null);
}
@Handler
void onSaveSourceDocAs()
{
saveNewFile(isUserSaved_ ? getPath() : null);
}
@Handler
public void onGotoProfileSource()
{
FilePosition filePosition = FilePosition.create(selectedLine_, 0);
fileTypeRegistry_.editFile(FileSystemItem.createFile(selectedPath_),
filePosition);
}
public String getDefaultNamePrefix()
{
return "Profile";
}
private void savePropertiesWithPath(String path)
{
String name = FileSystemItem.getNameFromPath(path);
persistDocumentProperty("name", name);
persistDocumentProperty("path", path);
getName().setValue(name, true);
name_.fireChangeEvent();
}
private ProfilerContents getContents()
{
return doc_.getProperties().cast();
}
private void persistDocumentProperty(String property, String value)
{
HashMap<String, String> props = new HashMap<String, String>();
props.put(property, value);
sourceServer_.modifyDocumentProperties(
doc_.getId(),
props,
new SimpleRequestCallback<Void>("Error")
{
@Override
public void onResponseReceived(Void response)
{
}
@Override
public void onError(ServerError error)
{
Debug.logError(error);
globalDisplay_.showErrorMessage("Failed to Save Profile Properties",
error.getMessage());
}
});
}
private void saveNewFile(final String suggestedPath)
{
FileSystemItem fsi;
if (suggestedPath != null)
fsi = FileSystemItem.createFile(suggestedPath).getParentPath();
else
fsi = workbenchContext_.getDefaultFileDialogDir();
fileDialogs_.saveFile(
"Save File - " + getName().getValue(),
fileContext_,
fsi,
fileType_.getDefaultExtension(),
false,
new ProgressOperationWithInput<FileSystemItem>()
{
public void execute(final FileSystemItem saveItem,
final ProgressIndicator indicator)
{
if (saveItem == null)
return;
workbenchContext_.setDefaultFileDialogDir(
saveItem.getParentPath());
final String toPath = saveItem.getPath();
server_.copyProfile(
htmlLocalPath_,
toPath,
new ServerRequestCallback<JavaScriptObject>() {
@Override
public void onResponseReceived(JavaScriptObject response)
{
savePropertiesWithPath(saveItem.getPath());
persistDocumentProperty("isUserSaved", "saved");
isUserSaved_ = true;
indicator.onCompleted();
}
@Override
public void onError(ServerError error)
{
Debug.logError(error);
indicator.onCompleted();
globalDisplay_.showErrorMessage("Failed to Save Profile",
error.getMessage());
}
});
}
});
}
private Command postSaveProfileCommand()
{
return new Command()
{
public void execute()
{
}
};
}
private void onMessage(final String message,
final String file,
final String normPath,
final String details,
final int line)
{
if (message == "sourcefile")
{
server_.profileSources(file, normPath, new ServerRequestCallback<String>()
{
@Override
public void onResponseReceived(String response)
{
selectedLine_ = line;
hasValidPath_ = !StringUtil.isNullOrEmpty(response);
selectedPath_ = hasValidPath_ ? response : file;
commands_.gotoProfileSource().setEnabled(hasValidPath_);
if (details == "open")
{
if (hasValidPath_)
{
FilePosition filePosition = FilePosition.create(line, 0);
CodeNavigationTarget navigationTarget = new CodeNavigationTarget(response, filePosition);
fileTypeRegistry_.editFile(
FileSystemItem.createFile(navigationTarget.getFile()),
filePosition);
}
else if (selectedPath_.indexOf("<expr>") == -1)
{
globalDisplay_.showMessage(GlobalDisplay.MSG_ERROR,
"Error while opening profiler source",
"The source file " + selectedPath_ + " does not exist.");
}
}
}
@Override
public void onError(ServerError error)
{
Debug.logError(error);
}
});
}
}
public static void onGlobalMessage(final String message,
final String file,
final String normPath,
final String details,
final int line)
{
if (activeProfilerEditingTarger_ != null)
{
activeProfilerEditingTarger_.onMessage(message, file, normPath, details, line);
}
}
@Handler
void onPopoutDoc()
{
events_.fireEvent(new PopoutDocEvent(getId(), currentPosition()));
}
@Handler
void onReturnDocToMain()
{
events_.fireEventToMainWindow(new DocWindowChangedEvent(
getId(), SourceWindowManager.getSourceWindowId(), "",
DocTabDragParams.create(getId(), currentPosition()),
null, 0));
}
private native static void initializeEvents() /*-{
var handler = $entry(function(e) {
if (typeof e.data != 'object')
return;
if (e.origin.substr(0, e.origin.length) != $wnd.location.origin)
return;
if (e.data.source != "profvis")
return;
@org.rstudio.studio.client.workbench.views.source.editors.profiler.ProfilerEditingTarget::onGlobalMessage(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)(
e.data.message,
e.data.file,
e.data.normpath,
e.data.details,
e.data.line
);
});
$wnd.addEventListener("message", handler, true);
}-*/;
private SourceDocument doc_;
private ProfilerEditingTargetWidget view_;
private final ProfilerPresenter presenter_;
private final Value<Boolean> neverDirtyState_ = new Value<Boolean>(false);
private final EventBus events_;
private final Commands commands_;
private final ProfilerServerOperations server_;
private final SourceServerOperations sourceServer_;
private final GlobalDisplay globalDisplay_;
private Provider<SourceWindowManager> pSourceWindowManager_;
private final FileDialogs fileDialogs_;
private final RemoteFileSystemContext fileContext_;
private final WorkbenchContext workbenchContext_;
private final EventBus eventBus_;
private Provider<String> defaultNameProvider_;
private final FileTypeRegistry fileTypeRegistry_;
private ProfilerType fileType_ = new ProfilerType();
private HandlerRegistration commandHandlerReg_;
private boolean htmlPathInitialized_;
private static boolean initializedEvents_;
private Value<String> name_ = new Value<String>(null);
private String tempName_;
private String htmlPath_;
private String htmlLocalPath_;
private boolean isUserSaved_;
private static ProfilerEditingTarget activeProfilerEditingTarger_;
private String selectedPath_;
private int selectedLine_;
private Boolean hasValidPath_ = false;
}