package com.limegroup.gnutella.gui; import java.io.File; import java.util.Set; import java.util.Vector; import javax.swing.SwingUtilities; import com.limegroup.gnutella.ActivityCallback; import com.limegroup.gnutella.Connection; import com.limegroup.gnutella.Downloader; import com.limegroup.gnutella.FileManagerEvent; import com.limegroup.gnutella.GUID; import com.limegroup.gnutella.MediaType; import com.limegroup.gnutella.RemoteFileDesc; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.Uploader; import com.limegroup.gnutella.browser.MagnetOptions; import com.limegroup.gnutella.chat.Chatter; import com.limegroup.gnutella.gui.chat.ChatUIManager; import com.limegroup.gnutella.gui.download.DownloaderUtils; import com.limegroup.gnutella.gui.search.SearchInformation; import com.limegroup.gnutella.gui.search.SearchMediator; import com.limegroup.gnutella.search.HostData; import com.limegroup.gnutella.settings.DaapSettings; import com.limegroup.gnutella.settings.QuestionsHandler; import com.limegroup.gnutella.settings.iTunesSettings; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.StringUtils; import com.limegroup.gnutella.util.Switch; import com.limegroup.gnutella.version.UpdateInformation; /** * This class is the gateway from the backend to the frontend. It * delegates all callbacks to the appropriate frontend classes, and it * also handles putting calls onto the Swing thread as necessary. * * It implements the <tt>ActivityCallback</tt> callback interface, designed * to make it easy to swap UIs. */ public final class VisualConnectionCallback implements ActivityCallback { /** * Constructs a new VisualConnectionCallback. */ VisualConnectionCallback() {} /////////////////////////////////////////////////////////////////////////// // Connection-related callbacks /////////////////////////////////////////////////////////////////////////// /** * Handle a new connection. */ public void connectionInitializing(Connection c) { Runnable doWorkRunnable = new ConnectionInitializing(c); SwingUtilities.invokeLater(doWorkRunnable); } /** * Change the status of a connection when it's been fully initialized */ public void connectionInitialized(Connection c) { Runnable doWorkRunnable = new ConnectionInitialized(c); SwingUtilities.invokeLater(doWorkRunnable); } /** * Handle a removed connection. */ public void connectionClosed(Connection c) { Runnable doWorkRunnable = new ConnectionClosed(c); SwingUtilities.invokeLater(doWorkRunnable); } /** * Handle a new connection. */ private class ConnectionInitializing implements Runnable { private Connection c; public ConnectionInitializing(Connection c) { this.c = c; } public void run() { mf().getConnectionMediator().add(c); } } /** * Change the status of a connection when it's been fully initialized */ private class ConnectionInitialized implements Runnable { private Connection c; public ConnectionInitialized(Connection c) { this.c = c; } public void run() { mf().getConnectionMediator().update(c); } } /** * Handle a removed connection. */ private class ConnectionClosed implements Runnable { private Connection c; public ConnectionClosed(Connection c) { this.c = c; } public void run() { mf().getConnectionMediator().remove(c); } } /////////////////////////////////////////////////////////////////////////// // Query-related callbacks /////////////////////////////////////////////////////////////////////////// /** * Handle to the class that handles query strings. */ private final HandleQueryString HANDLE_QUERY_STRING = new HandleQueryString(); /** * Add a query string to the monitor screen */ public void handleQueryString(String query) { HANDLE_QUERY_STRING.addQueryString(query); } /** * Add a query reply to a query screen */ public void handleQueryResult(final RemoteFileDesc rfd, final HostData data, final Set locs) { SwingUtilities.invokeLater(new Runnable() { public void run() { SearchMediator.handleQueryResult(rfd, data, locs); } }); } /** * @return true if the guid is still viewable to the user, else false. */ public boolean isQueryAlive(GUID guid) { return SearchMediator.queryIsAlive(guid); } /** * Add a query string to the monitor screen */ private class HandleQueryString implements Runnable { private Vector list; private boolean active; public HandleQueryString() { list = new Vector(); active = false; } public void addQueryString(String query) { list.add(query); if (active == false) { active = true; SwingUtilities.invokeLater(this); } } public void run() { String query; while (list.size() > 0) { query = (String) list.elementAt(0); list.remove(0); mf().getMonitorView().handleQueryString(query); } active = false; } } /////////////////////////////////////////////////////////////////////////// // Files-related callbacks /////////////////////////////////////////////////////////////////////////// /** * File manager finished loading. */ public void fileManagerLoaded() { if (DaapSettings.DAAP_ENABLED.getValue()) { Runnable r = new Runnable() { public void run() { DaapManager.instance().fileManagerLoaded(); } }; GUIMediator.instance().schedule(r); } } /** * This method notifies the frontend that the data for the * specified shared <tt>File</tt> instance has been * updated. * * @param file the <tt>File</tt> instance for the shared file whose * data has been updated */ public void handleSharedFileUpdate(final File file) { /** * NOTE: Pass this off directly to the library * so it can discard the update if the directory * of the file isn't selected. * This reduces the amount of Runnables created * by a very large amount. */ mf().getLibraryMediator().updateSharedFile(file); } /** * Handles events created by the FileManager. Passes these events on to DAAP * or the Library. */ public void handleFileEvent(final FileManagerEvent evt) { if (DaapSettings.DAAP_ENABLED.getValue() && DaapManager.instance().isEnabled()) { Runnable r = new Runnable() { public void run() { DaapManager.instance().handleFileManagerEvent(evt); } }; GUIMediator.instance().schedule(r); } SwingUtilities.invokeLater(new Runnable() { public void run() { mf().getLibraryMediator().handleFileManagerEvent(evt); } }); } public void fileManagerLoading() { SwingUtilities.invokeLater(new Runnable() { public void run() { mf().getLibraryMediator().clearLibrary(); } }); if (DaapSettings.DAAP_ENABLED.getValue()) { Runnable r = new Runnable() { public void run() { DaapManager.instance().fileManagerLoading(); } }; GUIMediator.instance().schedule(r); } } /////////////////////////////////////////////////////////////////////////// // Download-related callbacks /////////////////////////////////////////////////////////////////////////// public void addDownload(Downloader mgr) { Runnable doWorkRunnable = new AddDownload(mgr); SwingUtilities.invokeLater(doWorkRunnable); } public void removeDownload(Downloader mgr) { Runnable doWorkRunnable = new RemoveDownload(mgr); SwingUtilities.invokeLater(doWorkRunnable); if (iTunesSettings.ITUNES_SUPPORT_ENABLED.getValue() && mgr.getState() == Downloader.COMPLETE) { iTunesMediator.instance().addSong(mgr.getSaveFile()); } } public void downloadsComplete() { Finalizer.setDownloadsComplete(); } /** * Show active downloads */ public void showDownloads() { SwingUtilities.invokeLater(new Runnable() { public void run() { GUIMediator.instance().setWindow(GUIMediator.SEARCH_INDEX); } }); } private class AddDownload implements Runnable { private Downloader mgr; public AddDownload(Downloader mgr) { this.mgr = mgr; } public void run() { mf().getDownloadMediator().add(mgr); } } private class RemoveDownload implements Runnable { private Downloader mgr; public RemoveDownload(Downloader mgr) { this.mgr = mgr; } public void run() { mf().getDownloadMediator().remove(mgr); mf().getLibraryMediator().quickRefresh(); SearchMediator.updateResults(); } } /////////////////////////////////////////////////////////////////////////// // Upload-related callbacks /////////////////////////////////////////////////////////////////////////// public void addUpload(Uploader mgr) { Runnable doWorkRunnable = new AddUpload(mgr); SwingUtilities.invokeLater(doWorkRunnable); } public void removeUpload(Uploader mgr) { Runnable doWorkRunnable = new RemoveUpload(mgr); SwingUtilities.invokeLater(doWorkRunnable); } public void uploadsComplete() { Finalizer.setUploadsComplete(); } private class AddUpload implements Runnable { private Uploader up; public AddUpload(Uploader up) { this.up = up; } public void run() { mf().getUploadMediator().add(up); } } private class RemoveUpload implements Runnable { private Uploader mgr; public RemoveUpload(Uploader mgr) { this.mgr = mgr; } public void run() { mf().getUploadMediator().remove(mgr); } } /////////////////////////////////////////////////////////////////////////// // Chat-related callbacks /////////////////////////////////////////////////////////////////////////// /** * Adds a new chat session, encapsulated in the specified * <tt>Chatter</tt> instance. * * @param chatter the <tt>Chatter</tt> instance that provides all * data access regarding the chat session */ public void acceptChat(final Chatter chatter) { SwingUtilities.invokeLater(new Runnable() { public void run() { ChatUIManager.instance().acceptChat(chatter); } }); } /** * Receives a new chat message for a specific <tt>Chatter</tt> * instance. * * @param chatter the <tt>Chatter</tt> instance that is receiving * a new message */ public void receiveMessage(final Chatter chatter) { SwingUtilities.invokeLater(new Runnable() { public void run() { ChatUIManager.instance().receiveMessage(chatter); } }); } /** * Specifies that the given chat host is no longer available, thereby * ending the chat session. * * @param chatter the <tt>Chatter</tt> instance for the chat session * that is terminating */ public void chatUnavailable(final Chatter chatter) { SwingUtilities.invokeLater(new Runnable() { public void run() { ChatUIManager.instance().chatUnavailable(chatter); } }); } /** * Display an error message for the specified chat session. * * @param chatter the <tt>Chatter</tt> instance to show an error for * @param str the error to display */ public void chatErrorMessage(final Chatter chatter, final String str) { SwingUtilities.invokeLater(new Runnable() { public void run() { ChatUIManager.instance().chatErrorMessage(chatter, str); } }); } /////////////////////////////////////////////////////////////////////////// // Other stuff /////////////////////////////////////////////////////////////////////////// /** * Notification that the address has changed. */ public void addressStateChanged() { SwingUtilities.invokeLater(new Runnable() { public void run() { // don't touch GUI code if it isn't constructed. // this is necessary here only because addressStateChanged // is triggered by Acceptor, which is init'd prior to the // GUI actually existing. if (GUIMediator.isConstructed()) SearchMediator.addressChanged(); } }); } public void disconnected() { SwingUtilities.invokeLater(new Runnable() { public void run() { if (GUIMediator.isConstructed()) GUIMediator.disconnected(); } }); } /** * Pops up a dialog that the user is attempting to share a sensitive * directory, and allows the user to either share or not share * the folder. Returns true if the sensitive directory should be shared. */ public boolean warnAboutSharingSensitiveDirectory(final File dir) { final Switch share = new Switch(); GUIMediator.safeInvokeAndWait(new Runnable() { public void run() { // Use unicode char for non-breaking space so that // directory name is listed all on one line. String dirName = GUIUtils.convertToNonBreakingSpaces(4, dir.getAbsolutePath()); int retval = GUIMediator.showYesNoMessage( "MESSAGE_SENSITIVE_SHARE_TOP", "\n\n" + dirName + "\n\n", "MESSAGE_SENSITIVE_SHARE_BOTTOM"); if (retval == MessageService.YES_OPTION) { share.turnOn(); RouterService.getFileManager().validateSensitiveFile(dir); } else { share.turnOff(); RouterService.getFileManager().invalidateSensitiveFile(dir); } } }); return share.isOn(); } public void setAnnotateEnabled(final boolean enabled) { SwingUtilities.invokeLater(new Runnable() { public void run() { mf().getLibraryMediator().setAnnotateEnabled(enabled); } }); } /** * Notification that a new update is available. */ public void updateAvailable(UpdateInformation update) { GUIMediator.instance().showUpdateNotification(update); } /** * Display an error message for a ResultPanel (if it still exists) * @param guid The GUID of the ResultPanel. */ public void browseHostFailed(final GUID guid) { SwingUtilities.invokeLater(new Runnable() { public void run() { SearchMediator.browseHostFailed(guid); } }); } /** * Shows the user a message informing her that a file being downloaded * is corrupt. * <p> * This method MUST call dloader.discardCorruptDownload(boolean b) * otherwise there will be threads piling up waiting for a notification */ public void promptAboutCorruptDownload(Downloader downloader) { final Downloader dloader = downloader; SwingUtilities.invokeLater(new Runnable() { public void run() { int resp=GUIMediator.showYesNoMessage( "MESSAGE_FILE_CORRUPT", dloader.getSaveFile().getName(), "MESSAGE_CONTINUE_DOWNLOAD", QuestionsHandler.CORRUPT_DOWNLOAD); // discard if they didn't want to save. dloader.discardCorruptDownload( resp == MessageService.NO_OPTION); } }); } /** * Tell the GUI to deiconify. */ public void restoreApplication() { SwingUtilities.invokeLater(new Runnable() { public void run() { GUIMediator.restoreView(); } }); } /** * Notification of a component loading. */ public void componentLoading(final String component) { SwingUtilities.invokeLater(new Runnable() { public void run() { GUIMediator.setSplashScreenString( GUIMediator.getStringResource("SPLASH_STATUS_COMPONENT_LOADING_" + component)); } }); } /** * Indicates that the firewalled state of this has changed. */ public void acceptedIncomingChanged(final boolean status) { SwingUtilities.invokeLater(new Runnable() { public void run() { GUIMediator.instance().getStatusLine().updateFirewallLabel(status); } }); } public String getHostValue(String key) { return GUIMediator.getStringResource(key); } /** * Returns the MainFrame. */ private MainFrame mf() { return GUIMediator.instance().getMainFrame(); } /** * Returns true since we want to kick off the magnet downloads ourselves. */ public boolean handleMagnets(final MagnetOptions[] magnets) { SwingUtilities.invokeLater(new Runnable() { public void run() { boolean oneSearchStarted = false; for (int i = 0; i < magnets.length; i++) { // spawn search for keyword only magnet if (magnets[i].isKeywordTopicOnly() && !oneSearchStarted) { String query = StringUtils.createQueryString (magnets[i].getKeywordTopic()); SearchInformation info = SearchInformation.createKeywordSearch (query, null, MediaType.getAnyTypeMediaType()); if (SearchMediator.validateInfo(info) == SearchMediator.QUERY_VALID) { oneSearchStarted = true; SearchMediator.triggerSearch(info); } } else { DownloaderUtils.createDownloader(magnets[i]); } } if (magnets.length > 0) { GUIMediator.instance().setWindow(GUIMediator.SEARCH_INDEX); } } }); return true; } }