package floobits.impl;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import floobits.Listener;
import floobits.common.*;
import floobits.common.interfaces.IContext;
import floobits.common.interfaces.IFile;
import floobits.common.protocol.FlooUser;
import floobits.common.protocol.handlers.FlooHandler;
import floobits.dialogs.*;
import floobits.utilities.Colors;
import floobits.utilities.Flog;
import floobits.utilities.IntelliUtils;
import floobits.windows.FloobitsWindowManager;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* I am the link between a project and floobits
*/
public class ContextImpl extends IContext {
static public class BalloonState {
public Image smallGravatar;
public Image largeGravatar;
public int lineNumber;
public Balloon balloon;
}
private Listener listener = new Listener(this);
public ConcurrentHashMap<String, BalloonState> gravatars = new ConcurrentHashMap<String, BalloonState>();
public Project project;
public FloobitsWindowManager floobitsWindowManager;
private ExecutorService pool;
public ContextImpl(Project project) {
super();
this.project = project;
this.iFactory = new FactoryImpl(this, editor);
}
public void statusMessage(String message, NotificationType notificationType) {
Flog.statusMessage(message, notificationType, project);
}
@Override
public void loadFloobitsWindow() {
floobitsWindowManager = new FloobitsWindowManager(this);
}
@Override public void flashMessage(final String message) {
Flog.flashMessage(message, project);
}
@Override public void warnMessage(String message) {
Flog.log(message);
statusMessage(message, NotificationType.WARNING);
chatStatusMessage(message);
}
@Override public void statusMessage(String message) {
Flog.log(message);
if (floobitsWindowManager != null && !floobitsWindowManager.isOpen()) {
//Only show a status message when chat is not open.
statusMessage(message, NotificationType.INFORMATION);
}
chatStatusMessage(message);
}
@Override public void errorMessage(String message) {
Flog.error(message);
statusMessage(message, NotificationType.ERROR);
chatErrorMessage(message);
}
@Override
public boolean confirmDialog(String message) {
int answer = JOptionPane.showConfirmDialog(null, message);
return answer == JOptionPane.YES_OPTION;
}
@Override public void chatStatusMessage(String message) {
if (floobitsWindowManager != null) {
floobitsWindowManager.statusMessage(message);
}
}
@Override public void chatErrorMessage(String message) {
if (floobitsWindowManager != null) {
floobitsWindowManager.errorMessage(message);
}
}
@Override
public Object getActualContext() {
return project;
}
@Override
protected void shareProjectDialog(String name, List<String> orgs, final String host, final boolean _private_, final String projectPath) {
final ContextImpl context = this;
ShareProjectDialog shareProjectDialog = new ShareProjectDialog(name, orgs, project,
new RunLater<ShareProjectDialog>() {
@Override
public void run(ShareProjectDialog dialog) {
if (API.createWorkspace(host, dialog.getOrgName(), dialog.getWorkspaceName(), context, _private_)) {
FlooUrl url = new FlooUrl(host, dialog.getOrgName(), dialog.getWorkspaceName(), Constants.defaultPort, true);
joinWorkspace(url, projectPath, true, null);
}
}
},
new RunLater<ShareProjectDialog>() {
@Override
public void run(ShareProjectDialog dialog) {
FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, false, false, false);
descriptor.setTitle("Select folder to upload");
descriptor.setDescription("NOTE: You cannot choose a folder outside of your project.");
VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(projectPath));
VirtualFile[] vFiles = FileChooser.chooseFiles(descriptor, null, virtualFile);
if (vFiles.length < 1) {
Flog.warn("No directory selected for picking files to upload in share project.");
return;
}
if (API.createWorkspace(host, dialog.getOrgName(), dialog.getWorkspaceName(), context, _private_)) {
FlooUrl url = new FlooUrl(host, dialog.getOrgName(), dialog.getWorkspaceName(), Constants.defaultPort, true);
String filePath = vFiles[0].getCanonicalPath();
if (filePath == null) {
Flog.warn("Upload for picked directory in share project has a null path");
return;
}
IFile dirToAdd = iFactory.findFileByIoFile(new File(filePath));
joinWorkspace(url, projectPath, true, dirToAdd);
}
}
});
shareProjectDialog.createCenterPanel();
shareProjectDialog.show();
}
@Override
public void followUser() {
final FlooHandler flooHandler = this.getFlooHandler();
if (flooHandler == null) {
return;
}
HashMap<String, Boolean> usersToChoose = new HashMap<String, Boolean>();
String me = flooHandler.state.username;
for (FlooUser user : flooHandler.state.users.values()) {
if (user.username.equals(me)) {
continue;
}
if (user.client.equals("flootty")) {
continue;
}
if (Arrays.asList(user.perms).indexOf("highlight") == -1) {
continue;
}
usersToChoose.put(user.username, flooHandler.state.followedUsers.contains(user.username));
}
FollowUserDialog followUserDialog = new FollowUserDialog(usersToChoose, project, new RunLater<FollowUserDialog>() {
@Override
public void run(FollowUserDialog dialog) {
getFlooHandler().state.setFollowedUsers(dialog.getFollowedUsers());
}
});
followUserDialog.createCenterPanel();
followUserDialog.show();
}
@Override
public void updateFollowing() {
if (floobitsWindowManager != null) {
floobitsWindowManager.updateUserList();
}
}
@Override
public void connected() {
editor.reset();
if (pool != null) {
Flog.info("Pool wasn't null when creating a new one.");
}
pool = Executors.newFixedThreadPool(5);
}
@Override
public void removeUser(FlooUser user) {
statusMessage(String.format("%s left the workspace.", user.username));
if (floobitsWindowManager != null) {
floobitsWindowManager.removeUser(user);
}
iFactory.removeHighlightsForUser(user.user_id);
}
@Override
public synchronized void shutdown() {
super.shutdown();
if (floobitsWindowManager != null) {
floobitsWindowManager.clearUsers();
}
try {
listener.shutdown();
} catch (Throwable e) {
Flog.error(e);
}
listener = new Listener(this);
if (pool != null) {
pool.shutdownNow();
pool = null;
}
}
public void setListener(boolean b) {
listener.isListening.set(b);
}
public void setSaving(boolean b) {
listener.isSaving.set(b);
}
@Override
public void mainThread(Runnable runnable) {
ApplicationManager.getApplication().invokeLater(runnable);
}
@Override
public void readThread(final Runnable runnable) {
final ContextImpl context = this;
mainThread(new Runnable() {
@Override
public void run() {
try {
ApplicationManager.getApplication().runReadAction(runnable);
} catch (Throwable throwable) {
API.uploadCrash(context, throwable);
}
}
});
}
@Override
public void writeThread(final Runnable runnable) {
final long l = System.currentTimeMillis();
final ContextImpl context = this;
mainThread(new Runnable() {
@Override
public void run() {
CommandProcessor.getInstance().executeCommand(context.project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
long time = System.currentTimeMillis() - l;
if (time > 200) {
Flog.log("spent %s getting lock", time);
}
try {
runnable.run();
} catch (Throwable throwable) {
API.uploadCrash(context, throwable);
}
}
});
}
}, "Floobits", null);
}
});
}
@Override
public void dialog(String title, String body, RunLater<Boolean> runLater) {
DialogBuilder.build(title, body, runLater);
}
@Override
public void dialogDisconnect(int _tooMuch, int _howMany) {
NumberFormat numberFormat = NumberFormat.getNumberInstance();
String howMany = numberFormat.format(_howMany);
String tooMuch = numberFormat.format(_tooMuch);
String notice = String.format("You have too many directories that are over %s MB to upload with Floobits.", tooMuch);
DisconnectNoticeDialog disconnectNoticeDialog = new DisconnectNoticeDialog(new Runnable() {
@Override
public void run() {
shutdown();
}
}, String.format("%s We limit it to %d and you have %s big directories.", notice, Constants.TOO_MANY_BIG_DIRS, howMany));
disconnectNoticeDialog.createCenterPanel();
disconnectNoticeDialog.show();
}
@Override
public void dialogPermsRequest(final String username, final RunLater<String> runLater) {
final ContextImpl context = this;
mainThread(new Runnable() {
@Override
public void run() {
HandleRequestPermsRequestDialog d = new HandleRequestPermsRequestDialog(username, context, runLater);
d.createCenterPanel();
d.show();
}
});
}
@Override
public boolean dialogTooBig(HashMap<String, Integer> bigStuff) {
HandleTooBigDialog handleTooBigDialog = new HandleTooBigDialog(bigStuff);
handleTooBigDialog.createCenterPanel();
handleTooBigDialog.show();
return handleTooBigDialog.getExitCode() == 0;
}
@Override
public void dialogResolveConflicts(final Runnable stompLocal, final Runnable stompRemote, final boolean readOnly,
final Runnable flee, final String[] conflictedPathsArray,
final String[] connections) {
ProjectRootManager prm = ProjectRootManager.getInstance(project);
final AtomicInteger count = new AtomicInteger();
count.set(0);
for (VirtualFile file : prm.getContentRoots()) {
VfsUtil.iterateChildrenRecursively(file, null, new ContentIterator() {
@Override
public boolean processFile(VirtualFile file) {
FileImpl fileImpl = new FileImpl(file);
if (fileImpl.isValid()) {
count.getAndIncrement();
}
return true;
}
});
}
mainThread(new Runnable() {
@Override
public void run() {
FlooHandler flooHandler = getFlooHandler();
if (flooHandler == null) {
return;
}
ResolveConflictsDialog dialog = new ResolveConflictsDialog(stompLocal, stompRemote, readOnly, flee,
conflictedPathsArray, connections, flooHandler.state.numBufs(), count.get());
dialog.createCenterPanel();
dialog.show();
}
});
}
@Override
protected String selectAccount(String[] keys) {
SelectAccount selectAccount = new SelectAccount(project, keys);
selectAccount.show();
int exitCode = selectAccount.getExitCode();
if (exitCode != DialogWrapper.OK_EXIT_CODE) {
return null;
}
return selectAccount.getAccount();
}
@Override
public void chat(String username, String msg, Date messageDate) {
if (floobitsWindowManager == null) {
return;
}
if (!floobitsWindowManager.isOpen()) {
statusMessage(String.format("%s: %s", username, msg));
}
floobitsWindowManager.chatMessage(username, msg, messageDate);
}
@Override
public void openFloobitsWindow() {
if (floobitsWindowManager == null || floobitsWindowManager.isOpen()) {
return;
}
floobitsWindowManager.openFloobitsWindow();
floobitsWindowManager.updateUserList();
}
@Override
public void closeFloobitsWindow() {
if (floobitsWindowManager == null) {
return;
}
floobitsWindowManager.closeFloobitsWindow();
}
@Override
public void toggleFloobitsWindow() {
if (floobitsWindowManager == null) {
return;
}
floobitsWindowManager.toggleFloobitsWindow();
if (floobitsWindowManager.isOpen()) {
floobitsWindowManager.updateUserList();
}
}
@Override
public void setupFloobitsWindow() {
if (floobitsWindowManager != null) {
floobitsWindowManager.clearUsers();
}
openFloobitsWindow();
}
@Override
public void listenToEditor(EditorEventHandler editorEventHandler) {
listener.start(editorEventHandler);
}
@Override
public boolean isAccountAutoGenerated() {
return IntelliUtils.isAutoGenerated();
}
@Override
public void notifyCompleteSignUp() {
String url = IntelliUtils.getCompleteSignUpURL(project);
if (url == null) {
Flog.log("notifyCompleteSignUp: No pinocchio URL");
return;
}
Flog.info("Complete signup url is:", url);
chatStatusMessage(String.format("Your account was auto-created. Please %s.",
Utils.getLinkHTML(url, "click here to complete sign up")));
}
@Override
public void addUser(final FlooUser user) {
if (pool == null) {
Flog.info("Pool is null cannot add user.");
return;
}
if (user.color != null) {
Colors.color_map.put(user.username, user.color);
}
statusMessage(String.format("%s joined the workspace on %s (%s).", user.username, user.platform, user.client));
Flog.info("Adding gravatar for user %s.", user);
pool.execute(new Runnable() {
@Override
public void run() {
Image img;
URL url;
try {
url = new URL(user.gravatar);
} catch (MalformedURLException e) {
Flog.info("Could not create url for gravatar %s.", user.gravatar);
return;
}
try {
URLConnection con = url.openConnection();
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
InputStream in = con.getInputStream();
img = ImageIO.read(in);
} catch (IOException e) {
Flog.info("Could not load gravatar from network.");
return;
}
ContextImpl.BalloonState balloonState = new ContextImpl.BalloonState();
balloonState.largeGravatar = img.getScaledInstance(75, 75, Image.SCALE_SMOOTH);
balloonState.smallGravatar = img.getScaledInstance(30, 30, Image.SCALE_SMOOTH);
gravatars.put(user.gravatar, balloonState);
FlooHandler handler = getFlooHandler();
if (handler == null) {
return;
}
if (floobitsWindowManager != null) {
floobitsWindowManager.updateUserList();
}
}
});
if (floobitsWindowManager != null) {
floobitsWindowManager.addUser(user);
}
}
}