/** * Copyright 2009 Marc Stogaitis and Mimi Sun * * 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 org.gmote.server; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JOptionPane; import org.gmote.common.DataReceiverIF; import org.gmote.common.FileInfo; import org.gmote.common.TcpConnection; import org.gmote.common.FileInfo.FileSource; import org.gmote.common.FileInfo.FileType; import org.gmote.common.Protocol.Command; import org.gmote.common.Protocol.CommandType; import org.gmote.common.Protocol.ServerErrorType; import org.gmote.common.media.MediaMetaInfo; import org.gmote.common.packet.AbstractPacket; import org.gmote.common.packet.KeyboardEventPacket; import org.gmote.common.packet.LaunchUrlPacket; import org.gmote.common.packet.ListReplyPacket; import org.gmote.common.packet.ListReqPacket; import org.gmote.common.packet.MouseClickPacket; import org.gmote.common.packet.MouseMovePacket; import org.gmote.common.packet.MouseWheelPacket; import org.gmote.common.packet.RunFileReqPacket; import org.gmote.common.packet.ServerErrorPacket; import org.gmote.common.packet.SimplePacket; import org.gmote.common.packet.TileClickReq; import org.gmote.common.packet.TileSetReq; import org.gmote.server.media.MediaInfoUpdater; import org.gmote.server.media.MediaPlayerInterface; import org.gmote.server.media.MediaPlayerManager; import org.gmote.server.media.UnsupportedCommandException; import org.gmote.server.settings.BaseMediaPaths; import org.gmote.server.settings.DefaultSettings; import org.gmote.server.settings.DefaultSettingsEnum; import org.gmote.server.settings.SupportedFiletypeSettings; import org.gmote.server.updater.Updater; import org.gmote.server.visualtouchpad.VisualTouchpad; @SuppressWarnings("deprecation") public class GmoteServer implements DataReceiverIF { private static final Logger LOGGER = Logger.getLogger(GmoteServer.class.getName()); static final String VERSION = "2.0.2"; static final String MINIMUM_CLIENT_VERSION = "2.0.0"; private GmoteServerUi serverUi; MediaPlayerManager mediaPlayerManager; MediaPlayerInterface activeMediaPlayer = null; /** * Starts the server. * * @param ui * @param arguments * The command line arguments that were passed to the program in a * key=value format. (ex: loglevel=ALL) * @throws IOException */ void startServer(GmoteServerUi ui, String[] arguments) throws IOException { this.serverUi = ui; // Start a thread that will supply our ip to clients. int mouseUdpPort = MulticastServerThread.MULTICAST_LISTENING_PORT; try { mouseUdpPort = Integer.parseInt(DefaultSettings.instance().getSetting( DefaultSettingsEnum.UDP_PORT)); } catch (NumberFormatException e) { LOGGER .warning("There was an error reading the udp port from the config file. Using default setting. " + e.getMessage()); DefaultSettings.instance().setSetting(DefaultSettingsEnum.UDP_PORT, Integer.toString(mouseUdpPort)); } MulticastServerThread.listenForIpRequests(mouseUdpPort); // Initialize the media player manager. mediaPlayerManager = MediaPlayerManager.getInstance(); mediaPlayerManager.initialize(arguments); // Start the tcp threads that will handle connections. TcpConnectionHandler.instance(this).listenOnAllIpAddresses(); } /** * Called when a packet is received from the user. */ public synchronized void handleReceiveData(AbstractPacket packet, TcpConnection connection) { LOGGER.info("Received command: " + packet.toString()); Command command = packet.getCommand(); AbstractPacket returnPacket = null; if (command == Command.BASE_LIST_REQ) { // Return the base list of directories the client has access to. List<FileInfo> existingBasePaths = new ArrayList<FileInfo>(); for (FileInfo path : BaseMediaPaths.getInstance().getBasePaths()) { // Make sure that we only return paths that exist. if (new File(path.getAbsolutePath()).exists()) { existingBasePaths.add(path); } } // Get the files from the media library exposed by the media player. // TODO(mstogaitis): Right now, we're only looking at the files from the // music media player. We should look into how to handle cases where we // might have multiple media players (for example, if there's a vlc player // and windows media player binding installed). There's a tradeoff between // how loading media players that the user doesn't need (say he's only // using the default player and doesn't care that we support additional // players), vs being able to display media info from the media libraries // of all the media players we support. List<FileInfo> libraryList = mediaPlayerManager.getMediaPlayer(FileType.MUSIC) .getBaseLibraryFiles(); if (libraryList != null) { existingBasePaths.addAll(libraryList); } returnPacket = new ListReplyPacket(existingBasePaths.toArray(new FileInfo[existingBasePaths .size()])); } else if (command == Command.LIST_REQ) { // Return a list of files. ListReqPacket listReqPacket = (ListReqPacket) packet; if (listReqPacket.getFileInfo().getFileSource() == FileSource.MEDIA_LIBRARY) { List<FileInfo> libraryFiles = mediaPlayerManager.getMediaPlayer(FileType.MUSIC) .getLibrarySubFiles(listReqPacket.getFileInfo()); returnPacket = new ListReplyPacket(libraryFiles.toArray(new FileInfo[libraryFiles.size()])); } else { // The file is on the file system. String path = listReqPacket.getPath(); File file = new File(path); if (file.exists()) { if (ServerUtil.instance().isDvdDrive(file) && ServerUtil.instance().driveHasDvd(file)) { returnPacket = new SimplePacket(Command.PLAY_DVD); sendPacket(connection, returnPacket); returnPacket = null; runMedia(new FileInfo(path, path, FileType.DVD_DRIVE, false, FileSource.FILE_SYSTEM)); } else { returnPacket = createListFilesPacket(path); } } else { returnPacket = createFileNotExistErrorPacket(); } } } else if (command == Command.RUN) { // Run a file in its default application. FileInfo fileInfo = ((RunFileReqPacket) packet).getFileInfo(); returnPacket = runMedia(fileInfo); } else if (command.getCommandType() == CommandType.MEDIA_PLAYER) { // Handle media player operations (PAUSE, MUTE etc.). try { getMediaPlayer().controlPlayer(command); returnPacket = new SimplePacket(Command.SUCCESS); if (command == Command.CLOSE) { if (activeMediaPlayer != null && !activeMediaPlayer.isRunning()) { setActiveMediaPlayer((MediaPlayerInterface) null); } } } catch (UnsupportedCommandException e) { returnPacket = new ServerErrorPacket(ServerErrorType.UNSUPPORTED_COMMAND.ordinal(), e .getMessage()); LOGGER.log(Level.SEVERE, e.getMessage(), e); } } else if (command == Command.MOUSE_MOVE_REQ) { // We will move the mouse. // Depricated. We now send over udp. legacyMouseMove(packet); } else if (command == Command.MOUSE_CLICK_REQ) { // We will do a single click. TrackpadHandler.instance().hanldeMouseClickCommand((MouseClickPacket) packet); } else if (command == Command.KEYBOARD_EVENT_REQ) { TrackpadHandler.instance().handleKeyPressCommand((KeyboardEventPacket) packet); } else if (command == Command.MOUSE_WHEEL_REQ) { TrackpadHandler.instance().handleMouseWheelCommand((MouseWheelPacket) packet); } else if (command == Command.UPDATE_SERVER_REQUEST) { updateServer(); } else if (command == Command.SHOW_ALL_FILES_REQ) { DefaultSettings.instance().setSetting(DefaultSettingsEnum.SHOW_ALL_FILES, "true"); } else if (command == Command.SHOW_PLAYABLE_FILES_ONLY_REQ) { DefaultSettings.instance().setSetting(DefaultSettingsEnum.SHOW_ALL_FILES, "false"); } else if (command == Command.MEDIA_INFO_REQ) { returnPacket = MediaInfoUpdater.instance().handleMediaInfoReq(packet); } else if (command == Command.LAUNCH_URL_REQ) { String url = ((LaunchUrlPacket) packet).getUrl(); BrowserLauncherUtil.openURL(url); } else if (command == Command.TILE_SET_REQ) { VisualTouchpad.instance().tileUpdateRequest((TileSetReq) packet); } else if (command == Command.TILE_CLICK_REQ) { VisualTouchpad.instance().tileClickRequest((TileClickReq) packet); } else if (command == Command.TILE_INFO_REQ) { returnPacket = VisualTouchpad.instance().createScreenInfoReply(); VisualTouchpad.instance().clearTileImages(); } if (returnPacket != null) { sendPacket(connection, returnPacket); LOGGER.info("Sent reply to client"); } else if (command != Command.MOUSE_CLICK_REQ && command != Command.MOUSE_MOVE_REQ && command != Command.KEYBOARD_EVENT_REQ) { LOGGER.warning("Did not send a return packet for an incomming request: " + packet); } } private void sendPacket(TcpConnection connection, AbstractPacket returnPacket) { // Send a return packet to the client. try { connection.sendPacket(returnPacket); } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } } public class MediaRunner implements Runnable { FileInfo fileInfo; AbstractPacket returnPacket; public MediaRunner(FileInfo fileInfo) { this.fileInfo = fileInfo; // Initialise this with an error packet in case our thread gets cut off. returnPacket = new ServerErrorPacket(ServerErrorType.UNSPECIFIED_ERROR.ordinal(), "An unspecified error occurred"); } public AbstractPacket getReturnPacket() { return returnPacket; } public void run() { try { if (fileInfo.getFileSource() == FileSource.MEDIA_LIBRARY) { setActiveMediaPlayer(FileType.MUSIC); } else if (fileInfo.getFileType() == FileType.DVD_DRIVE) { setActiveMediaPlayer(FileType.DVD_DRIVE); } else if (new File(fileInfo.getAbsolutePath()).exists()) { setActiveMediaPlayer(fileInfo.getFileName()); } else { returnPacket = createFileNotExistErrorPacket(); return; } activeMediaPlayer.runFile(fileInfo); serverUi.addMediaPlayerControls(); returnPacket = new SimplePacket(Command.SUCCESS); } catch (UnsupportedEncodingException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); returnPacket = new ServerErrorPacket(ServerErrorType.UNSPECIFIED_ERROR.ordinal(), e .getMessage()); } catch (UnsupportedCommandException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); returnPacket = new ServerErrorPacket(ServerErrorType.UNSUPPORTED_COMMAND.ordinal(), e .getMessage()); } } } /** * Run a file. We are going to do this in a seperate thread since I've seen * cases where vlc gets blocked and blocks the rest of the application. We * should look into these cases and fix them so that we don't need this work * arround. * * @param fileName * @return */ private AbstractPacket runMedia(FileInfo fileInfo) { MediaRunner runner = new MediaRunner(fileInfo); Thread t = new Thread(runner, "MediaRunner"); t.start(); try { t.join();//20 * 1000); if (t.isAlive()) { // Kill the thread if it's still alive. t.interrupt(); reloadActiveMediaPlayer(); } } catch (InterruptedException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } return runner.getReturnPacket(); } private AbstractPacket createFileNotExistErrorPacket() { AbstractPacket returnPacket; returnPacket = new ServerErrorPacket(ServerErrorType.INVALID_FILE.ordinal(), "The file does not exist."); return returnPacket; } /** * Creates a packet with the list of the files in the current directory. */ ListReplyPacket createListFilesPacket(String directory) { File file = new File(directory); FileFilter filter = new FileFilter() { public boolean accept(File arg0) { if (arg0.isHidden()) { return false; } if (arg0.isDirectory()) { return true; } String name = arg0.getName().toLowerCase(); if (SupportedFiletypeSettings.fileNameToFileType(name) != FileType.UNKNOWN) { return true; } boolean showAllFiles = DefaultSettings.instance().getSetting( DefaultSettingsEnum.SHOW_ALL_FILES).equalsIgnoreCase("true"); if (showAllFiles) { return true; } return false; } }; File[] listOfFiles = file.listFiles(filter); FileInfo[] fileInfo = convertFileListToFileInfo(listOfFiles); Arrays.sort(fileInfo); ListReplyPacket packet = new ListReplyPacket(fileInfo); return packet; } private FileInfo[] convertFileListToFileInfo(File[] files) { FileInfo[] fileInfo = new FileInfo[files.length]; int index = 0; for (File file : files) { fileInfo[index] = ServerUtil.instance().fileInfoFromFile(file); index++; } return fileInfo; } public MediaPlayerInterface getMediaPlayer() { if (activeMediaPlayer == null) { activeMediaPlayer = mediaPlayerManager.getRunningMediaPlayer(); } return activeMediaPlayer; } private void setActiveMediaPlayer(FileType fileType) { setActiveMediaPlayer(mediaPlayerManager.getMediaPlayer(fileType)); MediaInfoUpdater.instance().sendMediaUpdate(activeMediaPlayer.getNewMediaInfo()); } private void setActiveMediaPlayer(String fileName) { setActiveMediaPlayer(mediaPlayerManager.getMediaPlayer(fileName)); MediaInfoUpdater.instance().sendMediaUpdate(activeMediaPlayer.getNewMediaInfo()); } private void setActiveMediaPlayer(MediaPlayerInterface player) { activeMediaPlayer = player; if (player == null) { LOGGER.info("Setting active media player to null"); MediaInfoUpdater.instance().sendMediaUpdate(new MediaMetaInfo(null, null, null, null, false)); } } private void reloadActiveMediaPlayer() { activeMediaPlayer = mediaPlayerManager .reloadMediaPlayer(activeMediaPlayer.getClass().getName()); } private void updateServer() { String updateUrl = ServerUtil.instance().getUpdateUrl(); try { String latestVersion = Updater.getLatestVersionNumber(updateUrl); boolean shouldIUpdate = Updater.askUserIfShouldUpdate(VERSION, latestVersion, false); if (!shouldIUpdate) { return; } } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); JOptionPane .showMessageDialog( null, "Unable to fetch update information. Plese try again later or visit www.gmote.org to download updates manually\n" + e.getMessage()); return; } LOGGER.warning("Updating server"); String installDirectory = ServerUtil.instance().findInstallDirectory(); String updaterPath = " org.gmote.server.updater.Updater "; String updaterArgs = " -classpath ." + File.pathSeparator + "bin/" + File.pathSeparator + "lib/swing-worker-1.2.jar "; ServerUtil.instance().startFileInSeparateprocess( "java " + updaterArgs + updaterPath + " " + VERSION + " " + updateUrl + " " + installDirectory + " sleepBeforeDownload ignoreConfirmDialog"); // Exit the application to allow the updater to run. System.exit(0); } // This handles the legacy 'tcp' mouse move packet. We now use a udp mechanism // but we'll keep this around for backwards compatibility. private void legacyMouseMove(AbstractPacket packet) { MouseMovePacket mmp = (MouseMovePacket) packet; TrackpadHandler.instance().handleMoveMouseCommand(mmp.getDiffX(), mmp.getDiffY()); } }