Exemplo n.º 1
0
class GUI(Ui_mainWindow):
    def __init__(self, window):
        super().__init__()
        self.setupUi(window)
        self.window = window
        self.settings = Settings()
        self.window.closeEvent = self.closeEvent
        self.server = Server()
        self.downman = DownloadManager()
        self.browser = Browser()
        # snapshot updater is to be started on exchange connect
        self.xchgClient = ExchangeClient()
        self.lastKnownDir = "/tmp"
        self.destPrefix = ''
        self.userlist = None
        self.di_list = []
        self.addEventListeners()
        self.browserTable.setColumnHidden(0, True)
        self.userListTable.setColumnHidden(0, True)
        self.tabWidget.setCurrentIndex(0)
        self.urlFrame.setVisible(False)
        self.window.setWindowIcon(QIcon(":/images/favicon.ico"))
        self.window.setWindowTitle("21Lane")
        self.makeMenuBar()
        self.setupSystemTray()
        self.loadSettings()
        self.window.show()

    def makeMenuBar(self):
        self.menuBar = QMenuBar(self.window)
        self.fileMenu = QMenu("File")
        self.menuBar.addMenu(self.fileMenu)
        self.exitAction = QAction("Exit", self.window)
        self.exitAction.triggered.connect(self.closeEvent)
        self.minimizeToTrayAction = QAction("Minimize to Tray", self.window)
        self.minimizeToTrayAction.setCheckable(True)
        self.minimizeToTrayAction.setChecked(True)
        self.fileMenu.addAction(self.minimizeToTrayAction)
        self.fileMenu.addAction(self.exitAction)
        self.window.layout().setMenuBar(self.menuBar)

    def loadSettings(self):
        success = self.settings.load()
        self.publicNameInput.setText(self.settings.configDic["publicName"])
        self.port.setValue(self.settings.configDic["port"])
        self.sharedLocationInput.setText(self.settings.configDic["sharedDir"])
        self.downloadLocationInput.setText(
            self.settings.configDic["downloadDir"])
        self.speedLimitSlider.setValue(self.settings.configDic["speedLimit"])
        self.speedLimitSpin.setValue(self.settings.configDic["speedLimit"])
        self.exchangeURLInput.setText(self.settings.configDic["exchangeURL"])
        if success:
            self.toggleShare()
            self.tabWidget.setCurrentIndex(1)
            self.reloadUsersBtn.click()

    def keyPressedEvent(self, event):
        if event.key() == Qt.Key_Escape:
            event.ignore()

    def closeEvent(self, event):
        if (self.window.sender()
                == None) and (self.minimizeToTrayAction.isChecked()):
            self.showWindow(False)
            self.activateAction.setChecked(False)
            event.ignore()
            return
        if self.server.isRunning():
            self.server.stopServer()
        if self.xchgClient.isRunning():
            print('attempting to shut down exchange client')
            self.xchgClient.quit()
            print('asked to end xchgclient politely')
            if not self.xchgClient.wait(1):
                print('forced closure of xchgclient required')
                self.xchgClient.terminate()
            print('xchgclient forcefully closed')
        if self.downman.running:
            self.downman.stopDownloader()
        qApp.exit()

    def showMessage(self, maintext=None, subtext=None):
        QMessageBox.information(self.window, maintext, subtext, QMessageBox.Ok,
                                QMessageBox.Ok)

    def getPathFromDialog(self):
        return QFileDialog.getExistingDirectory(self.window, "Select folder",
                                                self.lastKnownDir,
                                                QFileDialog.ShowDirsOnly)

    def addEventListeners(self):
        self.speedLimitSlider.valueChanged[int].connect(self.updateSpeedLimit)
        self.speedLimitSpin.valueChanged[int].connect(self.updateSpeedLimit)
        self.sharedLocationBtn.clicked.connect(self.showDirectorySelector)
        self.downloadLocationBtn.clicked.connect(self.showDirectorySelector)
        self.toggleShareBtn.setText("Start Sharing")
        self.toggleShareBtn.clicked.connect(self.toggleShare)
        self.server.ftp_handler.stats.clientConnect.connect(
            self.statClientConnected)
        self.server.ftp_handler.stats.clientDisconnect.connect(
            self.statClientDisconnected)
        self.server.ftp_handler.stats.fileTransfer[int].connect(
            self.statFileTransferred)
        self.reloadUsersBtn.clicked.connect(self.loadUsers)
        self.browserInput.returnPressed.connect(self.browserGoBtn.click)
        self.browserGoBtn.clicked.connect(self.loadBrowserTable)
        self.browserHomeBtn.clicked.connect(self.loadBrowserTable)
        self.browserPrevBtn.clicked.connect(self.handleBackBtnClick)
        self.userListTable.doubleClicked.connect(self.showBrowser)
        self.browserTable.doubleClicked.connect(self.handleFileSelection)
        self.developerLink.linkActivated.connect(xdg_open)
        self.projectLink.linkActivated.connect(xdg_open)

    def showDirectorySelector(self, event):
        if self.window.sender() is self.downloadLocationBtn:
            self.downloadLocationInput.setText(self.getPathFromDialog())
            self.destPrefix = self.downloadLocationInput.text()
        elif self.window.sender() is self.sharedLocationBtn:
            self.sharedLocationInput.setText(self.getPathFromDialog())

    def updateSpeedLimit(self, value):
        self.speedLimitSlider.setValue(value)
        self.speedLimitSpin.setValue(value)

    def statClientConnected(self):
        self.server.connected += 1
        self.stats_connected.setText(str(self.server.connected))

    def statClientDisconnected(self):
        self.server.connected -= 1
        self.stats_connected.setText(str(self.server.connected))

    def statFileTransferred(self, filesize):
        self.server.bytesTransferred += filesize
        self.server.filesTransferred += 1
        self.stats_files.setText(str(self.server.filesTransferred))
        self.stats_bytes.setText(toHumanReadable(self.server.bytesTransferred))

    def toggleShare(self):
        try:
            if  (not self.publicNameInput.text()) or \
                (not self.port.value()) or \
                (not self.sharedLocationInput.text()):
                raise FormIncompleteError
            if self.xchgClient.isRunning():
                self.xchgClient.quit()
                if not self.xchgClient.wait(1):
                    self.xchgClient.terminate()
            if self.server.isRunning():
                self.server.stopServer()
                self.toggleShareBtn.setText("Start Sharing")
                self.toggleShareBtn.setIcon(QIcon(":/images/failed.svg"))
                self.urlFrame.setVisible(False)
            else:
                self.server.setPort(self.port.value())
                self.server.setSharedDirectory(self.sharedLocationInput.text())
                self.server.start()
                if not self.exchangeURLInput.text():
                    self.xchgClient.updateInfo(self.publicNameInput.text(),
                                               None, self.port.value())
                else:
                    self.xchgClient.updateInfo(self.publicNameInput.text(),
                                               self.exchangeURLInput.text(),
                                               self.port.value())
                self.xchgClient.updateDir(self.sharedLocationInput.text())
                self.settings.update(self.publicNameInput.text(), self.port.value(), \
                    self.sharedLocationInput.text(), self.downloadLocationInput.text(), self.speedLimitSlider.value(), self.exchangeURLInput.text())
                self.toggleShareBtn.setText("Stop Sharing")
                self.toggleShareBtn.setIcon(QIcon(":/images/complete.svg"))
                addresses = getAllAddresses()
                print(addresses)
                if len(addresses) != 0:
                    lblstr = "<html><body>"
                    current = 0
                    end = len(addresses) - 1
                    for addr in addresses:
                        hyperlink = 'ftp://' + addr + ':' + str(
                            self.server.port)
                        lblstr += "<a href=\'" + hyperlink + "\'>" + hyperlink + "</a>"
                        if current != (end - 1):
                            lblstr += '<br>'
                        current += 1
                    lblstr += "</body></html>"
                    self.urlLabel.setText(lblstr)
                    self.urlFrame.setVisible(True)
                    print(self.urlFrame.isVisible(), 'url frame is visivle')
        except FileNotFoundError:
            self.showMessage("Don't fool me", "Shared location doesn't exist")
        except PortUnavailableError:
            self.showMessage("Port unavailable",
                             "Please select some other port number")
        except FormIncompleteError:
            self.showMessage("Form incomplete",
                             "Please fill in proper values!")

    def loadUsers(self):
        userlist = self.xchgClient.getUserList()
        self.userlist = userlist
        if not userlist:
            self.showMessage("Sorry", "Cannot retrieve list of users")
            return
        table = self.userListTable
        table.clearContents()
        table.setRowCount(len(userlist))
        for i, entry in enumerate(userlist):
            table.setItem(i, 0, QTableWidgetItem(str(i)))
            table.setItem(
                i, 1,
                QTableWidgetItem(toHumanReadable(int(entry["sharedSize"]))))
            table.setItem(i, 2, QTableWidgetItem(entry["publicName"]))

    def showBrowser(self):
        if not self.userlist:
            return
        current = self.userListTable.selectedItems()[0]
        index = int(self.userListTable.item(current.row(), 0).text())
        user = self.userlist[index]
        self.browser.update(user["ip"], int(user["port"]))
        self.tabWidget.setCurrentIndex(2)
        self.browserInput.setText("/")
        self.browserGoBtn.click()

    def loadBrowserTable(self):
        pwd = self.browserInput.text()
        filelist = []
        try:
            if not self.browser.pathExists(self.browser.host,
                                           self.browser.port, pwd):
                self.showMessage("Error", "The path does not exist!")
                return
            self.browser.historyStack.append(pwd)
            filelist = self.browser.getFileList(self.browser.host,
                                                self.browser.port, pwd)
            self.browser.filelist = filelist
        except ConnectionRefusedError:
            self.showMessage(
                "Offline",
                "The remote machine cannot be contacted!\nBetter luck next time."
            )
            self.tabWidget.setCurrentIndex(1)
        table = self.browserTable
        table.clearContents()
        table.setRowCount(len(filelist))
        for i, file in enumerate(filelist):
            table.setItem(i, 0, QTableWidgetItem(str(i)))
            table.setItem(i, 1,
                          QTableWidgetItem(QIcon(":images/download.png"), ""))
            table.setItem(i, 2,
                          QTableWidgetItem(toHumanReadable(file["filesize"])))
            if file["isDir"]:
                table.setItem(
                    i, 3, QTableWidgetItem(QIcon(":/images/folder.png"), ""))
            else:
                table.setItem(
                    i, 3, QTableWidgetItem(guess_mime(file["filename"])[0]))
            table.setItem(i, 4, QTableWidgetItem(file["filename"]))

    def handleBackBtnClick(self):
        if not self.userlist:
            self.showMessage(
                "Confused",
                "What should I load? \nFind someone from list of connected users."
            )
            return
        if len(self.browser.historyStack) < 2:
            self.showMessage("Sorry", "Hey, there's no looking back!")
            return
        self.browser.historyStack.pop()
        prev = self.browser.historyStack.pop()
        self.browserInput.setText(prev)
        self.loadBrowserTable()

    def handleFileSelection(self):
        if not self.browser.filelist:
            return
        current = self.browserTable.selectedItems()[0]
        index = int(self.browserTable.item(current.row(), 0).text())
        file = self.browser.filelist[index]
        print("index", index, file["pathname"], current.text())
        if file["isDir"] and current.column() is not 1:
            pwd = join_path(self.browserInput.text(), file["pathname"])
            self.browserInput.setText(pwd)
            self.browserGoBtn.click()
            return
        # a download is to be handled
        print("downloading directory", file["filename"])
        # decide it is a file or directory
        if not self.destPrefix:
            destDir = join_path(self.getPathFromDialog())
        else:
            destDir = self.destPrefix
        meta = None
        if file["isDir"]:
            meta = self.browser.getRecursiveFileList(self.browser.host,
                                                     self.browser.port,
                                                     file["pathname"])
            filelist = self.browser.recfilelist
        else:
            meta = {"totalFiles": 1, "totalSize": file["filesize"]}
            filelist = [file]
        signal = DownloadItemUpdater()
        diui = self.createDownloadItemBox(file["filename"], meta["totalSize"])
        dilist = []
        for item in filelist:
            di = DownloadItem(item["filename"], self.browser.host,
                              self.browser.port, item["pathname"],
                              join_path(destDir, item["filename"]),
                              item["filesize"], signal)
            di.updateGuiComponents(diui)
            dilist.append(di)

        # create callbacks for gui events
        def cancelCallback():
            for di in dilist:
                di.cancel()
            diui["cancelBtn"].setIcon(QIcon(":/images/reload.png"))
            diui["cancelBtn"].clicked.disconnect()
            diui["cancelBtn"].clicked.connect(retryCallback)

        def updateProgressCallback(progress):
            sum = 0
            for di in dilist:
                sum += di.completed
            diui["progress"].setValue(sum)
            diui["completion"].setText(toHumanReadable(sum))

        def retryCallback():
            print("retrying")
            di.completed = 0
            self.downman.addItem(di)
            diui["cancelBtn"].clicked.disconnect()
            diui["cancelBtn"].clicked.connect(cancelCallback)
            diui["cancelBtn"].setIcon(QIcon(":/images/cancel.png"))

        def errorCallback():
            diui["completion"].setText("Failed")
            cancelCallback()

        def completeCallback():
            diui["completion"].setText("Completed")
            diui["cancelBtn"].clicked.disconnect()
            diui["cancelBtn"].setToolTip("Open")
            diui["cancelBtn"].setIcon(QIcon(":/images/open.png"))
            diui["cancelBtn"].clicked.connect(openFile)

        def openFile():
            xdg_open(join_path(destDir, file["filename"]))

        def openDir():
            xdg_open(destDir)

        diui["cancelBtn"].clicked.connect(cancelCallback)
        signal.progress[int].connect(updateProgressCallback)
        signal.error.connect(errorCallback)
        signal.complete.connect(completeCallback)
        diui["openDestBtn"].clicked.connect(openDir)
        self.downloadsLayout.insertWidget(0, diui["widget"])
        for entry in dilist:
            self.downman.addItem(entry)
        # print ("downloading", di.filename, di.filesize, "to")

    def createDownloadItemBox(self, filename, filesize):
        diui = {}
        frame = QFrame(self.window)
        frame.setFrameStyle(QFrame.StyledPanel)
        layout = QHBoxLayout()
        filesize = filesize if not filesize is 0 else 1
        diui["layout"] = layout
        diui["widget"] = frame
        diui["filename"] = QLabel(filename)
        diui["filename"].setToolTip(filename)
        diui["filename"].setMaximumWidth(30)
        diui["filesize"] = QLabel(toHumanReadable(filesize))
        diui["filesize"].setAlignment(Qt.AlignCenter)
        diui["progress"] = QProgressBar()
        diui["progress"].setRange(0, filesize)
        diui["completion"] = QLabel("Waiting...")
        diui["completion"].setAlignment(Qt.AlignCenter)
        diui["cancelBtn"] = QPushButton(QIcon(":/images/cancel.png"), '')
        diui["openDestBtn"] = QPushButton(QIcon(":/images/folder.png"), '')
        diui["cancelBtn"].setToolTip("Cancel download")
        diui["openDestBtn"].setToolTip("Open folder")
        diui["layout"].addWidget(diui["filename"])
        diui["layout"].addWidget(diui["filesize"])
        diui["layout"].addWidget(diui["progress"])
        diui["layout"].addWidget(diui["completion"])
        diui["layout"].addWidget(diui["openDestBtn"])
        diui["layout"].addWidget(diui["cancelBtn"])
        diui["layout"].setSpacing(0)
        layout.setContentsMargins(5, 2, 5, 2)
        layout.setSpacing(6)
        layout.setStretch(0, 3)
        layout.setStretch(1, 2)
        layout.setStretch(2, 5)
        layout.setStretch(3, 2)
        layout.setStretch(4, 1)
        layout.setStretch(5, 1)
        frame.setLayout(layout)
        return diui

    def showWindow(self, checked):
        self.window.setVisible(checked)

    def setupSystemTray(self):
        self.activateAction = QAction("Show", self.window)
        self.activateAction.setCheckable(True)
        self.activateAction.setChecked(True)
        self.activateAction.triggered.connect(self.showWindow)
        self.quitAction = QAction("Quit", self.window)
        self.quitAction.triggered.connect(self.closeEvent)
        self.trayIconMenu = QMenu(self.window)
        self.trayIconMenu.addAction(self.activateAction)
        self.trayIconMenu.addAction(self.quitAction)
        self.trayIcon = QSystemTrayIcon(QIcon(":/images/icon.ico"),
                                        self.window)
        self.trayIcon.setContextMenu(self.trayIconMenu)
        self.trayIcon.show()
Exemplo n.º 2
0
class GameEngine:

    """A class representing the game engine.

    An instance of this class is to be created each time the program
    runs.  It doesn't handle thegraphical user interface, but centralizes
    about anything else:  the main configuration, world configuration
    of different games, aliases, macros, triggers and so on.  The
    GUI has a direct access to the engine and can therefore access it.

    """

    def __init__(self):
        self.logger = logger("")
        begin()
        self.settings = Settings(self)
        self.worlds = {}
        self.default_world = None
        self.level = Level.engine
        self.logger.info("CocoMUD engine started")

    def load(self):
        """Load the configuration."""
        self.logger.info("Loading the user's configuration...")
        self.settings.load()
        self.TTS_on = self.settings["options.TTS.on"]
        self.TTS_outside = self.settings["options.TTS.outside"]

        # For each world, set the game engine
        for world in self.worlds.values():
            world.engine = self

    def open(self, host, port, world):
        """Connect to the specified host and port.

        This method creates and returns a 'GUIClient' class initialized
        with the specified information.

        """
        self.logger.info("Creating a client for {host}:{port}".format(
                host=host, port=port))

        client = GUIClient(host, port, engine=self, world=world)
        sharp_engine = SharpScript(self, client, world)
        world.client = client
        client.sharp_engine = sharp_engine
        world.sharp_engine = sharp_engine
        return client

    def open_help(self, name):
        """Open the selected help file in HTML format.

        This method open the browser with the appropriate file.
        The file is the one in the user's language, unless it cannot
        be found.

        """
        lang = self.settings.get_language()
        filename = name + ".html"
        path = os.path.join("doc", lang, filename)
        if os.path.exists(path):
            self.logger.debug("Open the help file for {} (lang={})".format(
                    name, lang))
            os.startfile(path)
            return

        # Try English
        path = os.path.join("doc", "en", filename)
        if os.path.exists(path):
            self.logger.debug("Open the help file for {} (lang=en)".format(
                    name))
            os.startfile(path)
            return

        # Neither worked
        self.logger.debug("The documentation for the {} help file " \
                "cannot be found, either using lang={} or lang=en".format(
                name, lang))

    def get_world(self, name):
        """Return the selected world either by its name or location."""
        name = name.lower()
        for world in self.worlds.values():
            if world.name.lower() == name:
                return world
            elif world.location == name:
                return world

        return None

    def create_world(self, name):
        """Create a world."""
        world = World(name.lower())
        world.engine = self
        return world

    def prepare_world(self, world, merge=None):
        """Prepare the world, creating appropriate values."""
        if not world.sharp_engine:
            sharp_engine = SharpScript(self, None, world)
            world.sharp_engine = sharp_engine

        if merge is not None:
            if merge == "ignore":
                world.merging = MergingMethod.ignore
            elif merge == "replace":
                world.merging = MergingMethod.replace
            else:
                raise ValueError("unkwno merging method: {}".format(
                        merge))

    def stop(self):
        """Stop the game engine and close the sessions."""
        end()
Exemplo n.º 3
0
class GameEngine:

    """A class representing the game engine.

    An instance of this class is to be created each time the program
    runs.  It doesn't handle thegraphical user interface, but centralizes
    about anything else:  the main configuration, world configuration
    of different games, aliases, macros, triggers and so on.  The
    GUI has a direct access to the engine and can therefore access it.

    """

    def __init__(self, config_dir="."):
        self.logger = logger("")
        begin()
        self.config_dir = config_dir
        if config_dir != ".":
            self.logger.info(f"Using an alternative config directory: {config_dir}")

        self.settings = Settings(self, config_dir)
        self.sounds = True
        self.worlds = {}
        self.default_world = None
        self.level = Level.engine
        self.logger.info("CocoMUD engine started")

    def load(self):
        """Load the configuration."""
        self.logger.info("Loading the user's configuration...")
        self.settings.load()
        self.TTS_on = self.settings["options.TTS.on"]
        self.TTS_outside = self.settings["options.TTS.outside"]
        self.redirect_message = None

        # For each world, set the game engine
        for world in self.worlds.values():
            world.engine = self

    def open(self, host, port, world, session, panel=None):
        """Connect to the specified host and port.

        This method creates and returns a 'Factory' class initialized
        with the specified information.  It also tries to connect a
        client to this factory.

        """
        self.logger.info("Creating a client for {host}:{port}".format(
                host=host, port=port))

        self.prepare_world(world)
        factory = CocoFactory(world, session, panel)

        if world.protocol.lower() == "ssl":
            reactor.connectSSL(host, port, factory,
                    ssl.ClientContextFactory())
        else:
            reactor.connectTCP(host, port, factory)

        return factory

    def open_help(self, name):
        """Open the selected help file in HTML format.

        This method open the browser with the appropriate file.
        The file is the one in the user's language, unless it cannot
        be found.

        """
        lang = self.settings.get_language()
        filename = name + ".html"
        path = os.path.join(self.config_dir, "doc", lang, filename)
        if os.path.exists(path):
            self.logger.debug("Open the help file for {} (lang={})".format(
                    name, lang))
            os.startfile(path)
            return

        # Try English
        path = os.path.join(self.config_dir, "doc", "en", filename)
        if os.path.exists(path):
            self.logger.debug("Open the help file for {} (lang=en)".format(
                    name))
            os.startfile(path)
            return

        # Neither worked
        self.logger.warning("The documentation for the {} help file " \
                "cannot be found, either using lang={} or lang=en".format(
                name, lang))

    def get_world(self, name):
        """Return the selected world either by its name or location."""
        name = name.lower()
        for world in self.worlds.values():
            if world.name.lower() == name:
                return world
            elif world.location == name:
                return world

        return None

    def create_world(self, name):
        """Create a world."""
        world = World(name.lower())
        world.engine = self
        return world

    def prepare_world(self, world, merge=None):
        """Prepare the world, creating appropriate values."""
        if not world.sharp_engine:
            sharp_engine = SharpScript(self, None, world)
            world.sharp_engine = sharp_engine

        if merge is not None:
            if merge == "ignore":
                world.merging = MergingMethod.ignore
            elif merge == "replace":
                world.merging = MergingMethod.replace
            else:
                raise ValueError("unkwno merging method: {}".format(
                        merge))

    def stop(self):
        """Stop the game engine and close the sessions."""
        reactor.stop()