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()