diff options
| author | 2010-08-25 18:22:27 +0200 | |
|---|---|---|
| committer | 2010-08-25 18:22:27 +0200 | |
| commit | 29f9dc8fb3396b03d732ebcbeb1cc8f00fe13897 (patch) | |
| tree | f2a910cbea747a7b0c0a50d6c66691e54f5ef47f /interfaces/pyLoadGui.py | |
| parent | merged gui (diff) | |
| download | pyload-29f9dc8fb3396b03d732ebcbeb1cc8f00fe13897.tar.xz | |
new dirs
Diffstat (limited to 'interfaces/pyLoadGui.py')
| -rwxr-xr-x | interfaces/pyLoadGui.py | 720 | 
1 files changed, 720 insertions, 0 deletions
| diff --git a/interfaces/pyLoadGui.py b/interfaces/pyLoadGui.py new file mode 100755 index 000000000..6279fb10b --- /dev/null +++ b/interfaces/pyLoadGui.py @@ -0,0 +1,720 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +    This program is free software; you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation; either version 3 of the License, +    or (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +    See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program; if not, see <http://www.gnu.org/licenses/>. +     +    @author: mkaay +    @version: v0.4.0 +""" + +import sys + +from time import sleep + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from uuid import uuid4 as uuid +import re +import gettext +from xmlrpclib import Binary +from os.path import join +from os.path import abspath +from os.path import dirname + +sys.path.append(join(dirname(abspath(__file__)),"..")) + +from module import InitHomeDir +from module.gui.ConnectionManager import * +from module.gui.connector import Connector +from module.gui.MainWindow import * +from module.gui.Queue import * +from module.gui.Collector import * +from module.gui.XMLParser import * +from module.gui.CoreConfigParser import ConfigParser + +from os.path import expanduser +from os.path import join +from os.path import basename +from os.path import dirname +from os import chdir +from os import makedirs +from os import name as platform + +try: +    import pynotify +except ImportError: +    pass + +class main(QObject): +    def __init__(self): +        """ +            main setup +        """ +        QObject.__init__(self) +        self.app = QApplication(sys.argv) +        self.path = pypath +	self.homedir = self.getHomeDir() +         +        self.configdir = "" +         +        self.init(True) +     +    def getHomeDir(self): +        homedir = "" + +        if platform == 'nt': +            homedir = expanduser("~") +            if homedir == "~": +                import ctypes +                CSIDL_APPDATA = 26 +                _SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW +                _SHGetFolderPath.argtypes = [ctypes.wintypes.HWND, +                                             ctypes.c_int, +                                             ctypes.wintypes.HANDLE, +                                             ctypes.wintypes.DWORD, ctypes.wintypes.LPCWSTR] +             +                path_buf = ctypes.wintypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) +                result = _SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, path_buf) +                homedir = path_buf.value +        else: +            homedir = expanduser("~") +        return homedir +     +    def init(self, first=False): +        """ +            set main things up +        """ +        self.parser = XMLParser(join(self.configdir, "gui.xml"), join(self.path, "module", "config", "gui_default.xml")) +        lang = self.parser.xml.elementsByTagName("language").item(0).toElement().text() +        if not lang: +            parser = XMLParser(join(self.path, "module", "config", "gui_default.xml")) +            lang = parser.xml.elementsByTagName("language").item(0).toElement().text() + +        translation = gettext.translation("pyLoadGui", join(pypath, "locale"), languages=[str(lang)]) +        try: +            translation.install(unicode=(True if sys.stdout.encoding.lower().startswith("utf") else False)) +        except: +            translation.install(unicode=False) + +        self.connector = Connector() +        self.mainWindow = MainWindow(self.connector) +        self.connWindow = ConnectionManager() +        self.mainloop = self.Loop(self) +        self.connectSignals() +         +        self.checkClipboard = False +        default = self.refreshConnections() +        self.connData = None +        self.captchaProcessing = False +         +        if first: +            self.tray = TrayIcon() +            self.tray.show() +            self.notification = Notification(self.tray) +            self.connect(self, SIGNAL("showMessage"), self.notification.showMessage) +            self.connect(self.tray.exitAction, SIGNAL("triggered()"), self.app.quit) +            self.connect(self.tray.showAction, SIGNAL("toggled(bool)"), self.mainWindow.setVisible) +            self.connect(self.mainWindow, SIGNAL("hidden"), self.tray.mainWindowHidden) +         +        if not first: +            self.connWindow.show() +        else: +            self.connWindow.edit.setData(default) +            data = self.connWindow.edit.getData() +            self.slotConnect(data) +     +    def startMain(self): +        """ +            start all refresh threads and show main window +        """ +        if not self.connector.canConnect(): +            self.init() +            return +        self.connector.start() +        self.connect(self.connector, SIGNAL("connectionLost"), sys.exit) +        sleep(1) +        self.restoreMainWindow() +        self.mainWindow.show() +        self.initQueue() +        self.initPackageCollector() +        self.mainloop.start() +        self.clipboard = self.app.clipboard() +        self.connect(self.clipboard, SIGNAL('dataChanged()'), self.slotClipboardChange) +        self.mainWindow.actions["clipboard"].setChecked(self.checkClipboard) +         +        self.mainWindow.tabs["settings"]["w"].setConnector(self.connector) +        self.mainWindow.tabs["settings"]["w"].loadConfig() +        self.tray.showAction.setDisabled(False) +     +    def stopMain(self): +        """ +            stop all refresh threads and hide main window +        """ +        self.tray.showAction.setDisabled(True) +        self.disconnect(self.clipboard, SIGNAL('dataChanged()'), self.slotClipboardChange) +        self.disconnect(self.connector, SIGNAL("connectionLost"), sys.exit) +        self.mainloop.stop() +        self.connector.stop() +        self.mainWindow.saveWindow() +        self.mainWindow.hide() +        self.queue.stop() +        self.connector.wait() +     +    def connectSignals(self): +        """ +            signal and slot stuff, yay! +        """ +        self.connect(self.connector, SIGNAL("error_box"), self.slotErrorBox) +        self.connect(self.connWindow, SIGNAL("saveConnection"), self.slotSaveConnection) +        self.connect(self.connWindow, SIGNAL("removeConnection"), self.slotRemoveConnection) +        self.connect(self.connWindow, SIGNAL("connect"), self.slotConnect) +        self.connect(self.mainWindow, SIGNAL("connector"), self.slotShowConnector) +        self.connect(self.mainWindow, SIGNAL("addPackage"), self.slotAddPackage) +        self.connect(self.mainWindow, SIGNAL("setDownloadStatus"), self.slotSetDownloadStatus) +        self.connect(self.mainWindow, SIGNAL("saveMainWindow"), self.slotSaveMainWindow) +        self.connect(self.mainWindow, SIGNAL("pushPackageToQueue"), self.slotPushPackageToQueue) +        self.connect(self.mainWindow, SIGNAL("restartDownload"), self.slotRestartDownload) +        self.connect(self.mainWindow, SIGNAL("removeDownload"), self.slotRemoveDownload) +        self.connect(self.mainWindow, SIGNAL("abortDownload"), self.slotAbortDownload) +        self.connect(self.mainWindow, SIGNAL("addContainer"), self.slotAddContainer) +        self.connect(self.mainWindow, SIGNAL("stopAllDownloads"), self.slotStopAllDownloads) +        self.connect(self.mainWindow, SIGNAL("setClipboardStatus"), self.slotSetClipboardStatus) +        self.connect(self.mainWindow, SIGNAL("changePackageName"), self.slotChangePackageName) +        self.connect(self.mainWindow, SIGNAL("pullOutPackage"), self.slotPullOutPackage) +        self.connect(self.mainWindow, SIGNAL("setPriority"), self.slotSetPriority) +        self.connect(self.mainWindow, SIGNAL("reloadAccounts"), self.slotReloadAccounts) +         +        self.connect(self.mainWindow, SIGNAL("quit"), self.quit) +        self.connect(self.mainWindow.captchaDock, SIGNAL("done"), self.slotCaptchaDone) +     +    def slotShowConnector(self): +        """ +            emitted from main window (menu) +            hide the main window and show connection manager +            (to switch to other core) +        """ +        self.stopMain() +        self.init() +     +    def quit(self): +        """ +            quit gui +        """ +        self.app.quit() +     +    def loop(self): +        """ +            start application loop +        """ +        sys.exit(self.app.exec_()) +     +    def slotErrorBox(self, msg): +        """ +            display a nice error box +        """ +        msgb = QMessageBox(QMessageBox.Warning, "Error", msg) +        msgb.exec_() +     +    def initPackageCollector(self): +        """ +            init the package collector view +            * columns +            * selection +            * refresh thread +            * drag'n'drop +        """ +        view = self.mainWindow.tabs["collector"]["package_view"] +        view.setSelectionBehavior(QAbstractItemView.SelectRows) +        view.setSelectionMode(QAbstractItemView.ExtendedSelection) +        def dropEvent(klass, event): +            event.setDropAction(Qt.CopyAction) +            event.accept() +            view = event.source() +            if view == klass: +                items = view.selectedItems() +                for item in items: +                    if not hasattr(item.parent(), "getPackData"): +                        continue +                    target = view.itemAt(event.pos()) +                    if not hasattr(target, "getPackData"): +                        target = target.parent() +                    klass.emit(SIGNAL("droppedToPack"), target.getPackData()["id"], item.getFileData()["id"]) +                event.ignore() +                return +            items = view.selectedItems() +            for item in items: +                row = view.indexOfTopLevelItem(item) +                view.takeTopLevelItem(row) +        def dragEvent(klass, event): +            view = event.source() +            #dragOkay = False +            #items = view.selectedItems() +            #for item in items: +            #    if hasattr(item, "_data"): +            #        if item._data["id"] == "fixed" or item.parent()._data["id"] == "fixed": +            #            dragOkay = True +            #    else: +            #        dragOkay = True +            #if dragOkay: +            event.accept() +            #else: +            #    event.ignore() +        view.dropEvent = dropEvent +        view.dragEnterEvent = dragEvent +        view.setDragEnabled(True) +        view.setDragDropMode(QAbstractItemView.DragDrop) +        view.setDropIndicatorShown(True) +        view.setDragDropOverwriteMode(True) +        view.connect(view, SIGNAL("droppedToPack"), self.slotAddFileToPackage) +        #self.packageCollector = PackageCollector(view, self.connector) +        self.packageCollector = view.model() +     +    def initQueue(self): +        """ +            init the queue view +            * columns +            * progressbar +        """ +        view = self.mainWindow.tabs["queue"]["view"] +        view.setSelectionBehavior(QAbstractItemView.SelectRows) +        view.setSelectionMode(QAbstractItemView.ExtendedSelection) +        self.queue = view.model() +        self.queue.start() +     +    def refreshServerStatus(self): +        """ +            refresh server status and overall speed in the status bar +        """ +        status = self.connector.getServerStatus() +        if status["pause"]: +            status["status"] = _("Paused") +        else: +            status["status"] = _("Running") +        status["speed"] = int(status["speed"]) +        text = _("Status: %(status)s | Speed: %(speed)s kb/s") % status +        self.mainWindow.actions["toggle_status"].setChecked(not status["pause"]) +        self.mainWindow.serverStatus.setText(text) +     +    def refreshLog(self): +        """ +            update log window +        """ +        offset = self.mainWindow.tabs["log"]["text"].logOffset +        lines = self.connector.getLog(offset) +        if not lines: +            return +        self.mainWindow.tabs["log"]["text"].logOffset += len(lines) +        for line in lines: +            self.mainWindow.tabs["log"]["text"].emit(SIGNAL("append(QString)"), line.strip("\n")) +        cursor = self.mainWindow.tabs["log"]["text"].textCursor() +        cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) +        self.mainWindow.tabs["log"]["text"].setTextCursor(cursor) +     +    def getConnections(self): +        """ +            parse all connections in the config file +        """ +        connectionsNode = self.parser.xml.elementsByTagName("connections").item(0) +        if connectionsNode.isNull(): +            raise Exception("null") +        connections = self.parser.parseNode(connectionsNode) +        ret = [] +        for conn in connections: +            data = {} +            data["type"] = conn.attribute("type", "remote") +            data["default"] = conn.attribute("default", "False") +            data["id"] = conn.attribute("id", uuid().hex) +            if data["default"] == "True": +                data["default"] = True +            else: +                data["default"] = False +            subs = self.parser.parseNode(conn, "dict") +            if not subs.has_key("name"): +                data["name"] = _("Unnamed") +            else: +                data["name"] = subs["name"].text() +            if data["type"] == "remote": +                if not subs.has_key("server"): +                    continue +                else: +                    data["host"] = subs["server"].text() +                    data["ssl"] = subs["server"].attribute("ssl", "False") +                    if data["ssl"] == "True": +                        data["ssl"] = True +                    else: +                        data["ssl"] = False +                    data["user"] = subs["server"].attribute("user", "admin") +                    data["port"] = int(subs["server"].attribute("port", "7227")) +                    data["password"] = subs["server"].attribute("password", "") +            ret.append(data) +        return ret +     +    def slotSaveConnection(self, data): +        """ +            save connection to config file +        """ +        connectionsNode = self.parser.xml.elementsByTagName("connections").item(0) +        if connectionsNode.isNull(): +            raise Exception("null") +        connections = self.parser.parseNode(connectionsNode) +        connNode = self.parser.xml.createElement("connection") +        connNode.setAttribute("default", str(data["default"])) +        connNode.setAttribute("type", data["type"]) +        connNode.setAttribute("id", data["id"]) +        nameNode = self.parser.xml.createElement("name") +        nameText = self.parser.xml.createTextNode(data["name"]) +        nameNode.appendChild(nameText) +        connNode.appendChild(nameNode) +        if data["type"] == "remote": +            serverNode = self.parser.xml.createElement("server") +            serverNode.setAttribute("ssl", data["ssl"]) +            serverNode.setAttribute("user", data["user"]) +            serverNode.setAttribute("port", data["port"]) +            serverNode.setAttribute("password", data["password"]) +            hostText = self.parser.xml.createTextNode(data["host"]) +            serverNode.appendChild(hostText) +            connNode.appendChild(serverNode) +        found = False +        for c in connections: +            cid = c.attribute("id", "None") +            if str(cid) == str(data["id"]): +                found = c +                break +        if found: +            connectionsNode.replaceChild(connNode, found) +        else: +            connectionsNode.appendChild(connNode) +        self.parser.saveData() +        self.refreshConnections() +     +    def slotRemoveConnection(self, data): +        """ +            remove connection from config file +        """ +        connectionsNode = self.parser.xml.elementsByTagName("connections").item(0) +        if connectionsNode.isNull(): +            raise Exception("null") +        connections = self.parser.parseNode(connectionsNode) +        found = False +        for c in connections: +            cid = c.attribute("id", "None") +            if str(cid) == str(data["id"]): +                found = c +                break +        if found: +            connectionsNode.removeChild(found) +        self.parser.saveData() +        self.refreshConnections() +     +    def slotConnect(self, data): +        """ +            connect to a core +            if connection is local, parse the core config file for data +            set up connector, show main window +        """ +        self.connWindow.hide() +        if not data["type"] == "remote": +             +            coreparser = ConfigParser(self.configdir) +            if not coreparser.config: +                raise Exception +            #except: +            #    data["port"] = 7227 +            #    data["user"] = "admin" +            #    data["password"] = "pwhere" +            #    data["host"] = "127.0.0.1" +            #    data["ssl"] = False +             +            data["port"] = coreparser.get("remote","port") +            data["user"] = coreparser.get("remote","username") +            data["password"] = coreparser.get("remote","password") +            data["host"] = "127.0.0.1" +            data["ssl"] = coreparser.get("ssl","activated") +        data["ssl"] = "s" if data["ssl"] else "" +        server_url = "http%(ssl)s://%(user)s:%(password)s@%(host)s:%(port)s/" % data +        self.connector.setAddr(str(server_url)) +        self.startMain() +     +    def refreshConnections(self): +        """ +            reload connetions and display them +        """ +        self.parser.loadData() +        conns = self.getConnections() +        self.connWindow.emit(SIGNAL("setConnections"), conns) +        for conn in conns: +            if conn["default"]: +                return conn +        return None +     +    def slotSetDownloadStatus(self, status): +        """ +            toolbar start/pause slot +        """ +        self.connector.setPause(not status) +     +    def slotAddPackage(self, name, links): +        """ +            emitted from main window +            add package to the collector +        """ +        self.connector.proxy.add_package(name, links) +     +    def slotAddFileToPackage(self, pid, fid): +        """ +            emitted from collector view after a drop action +        """ +        self.connector.addFileToPackage(fid, pid) +     +    def slotAddContainer(self, path): +        """ +            emitted from main window +            add container +        """ +        filename = basename(path) +        type = "".join(filename.split(".")[-1]) +        fh = open(path, "r") +        content = fh.read() +        fh.close() +        self.connector.proxy.upload_container(filename, Binary(content)) +     +    def slotSaveMainWindow(self, state, geo): +        """ +            save the window geometry and toolbar/dock position to config file +        """ +        mainWindowNode = self.parser.xml.elementsByTagName("mainWindow").item(0) +        if mainWindowNode.isNull(): +            mainWindowNode = self.parser.xml.createElement("mainWindow") +            self.parser.root.appendChild(mainWindowNode) +        stateNode = mainWindowNode.toElement().elementsByTagName("state").item(0) +        geoNode = mainWindowNode.toElement().elementsByTagName("geometry").item(0) +        newStateNode = self.parser.xml.createTextNode(state) +        newGeoNode = self.parser.xml.createTextNode(geo) +         +        stateNode.removeChild(stateNode.firstChild()) +        geoNode.removeChild(geoNode.firstChild()) +        stateNode.appendChild(newStateNode) +        geoNode.appendChild(newGeoNode) +         +        self.parser.saveData() +     +    def restoreMainWindow(self): +        """ +            load and restore main window geometry and toolbar/dock position from config +        """ +        mainWindowNode = self.parser.xml.elementsByTagName("mainWindow").item(0) +        if mainWindowNode.isNull(): +            return +        nodes = self.parser.parseNode(mainWindowNode, "dict") +         +        state = str(nodes["state"].text()) +        geo = str(nodes["geometry"].text()) +         +        self.mainWindow.restoreWindow(state, geo) +        self.mainWindow.captchaDock.hide() +     +    def slotPushPackageToQueue(self, id): +        """ +            emitted from main window +            push the collector package to queue +        """ +        self.connector.proxy.push_package_to_queue(id) +     +    def slotRestartDownload(self, id, isPack): +        """ +            emitted from main window +            restart download +        """ +        if isPack: +            self.connector.restartPackage(id) +        else: +            self.connector.restartFile(id) +     +    def slotRemoveDownload(self, id, isPack): +        """ +            emitted from main window +            remove download +        """ +        if isPack: +            self.connector.removePackage(id) +        else: +            self.connector.removeFile(id) +     +    def slotAbortDownload(self, id, isPack): +        """ +            emitted from main window +            remove download +        """ +        if isPack: +            data = self.connector.proxy.get_package_data(id) +            self.connector.proxy.abort_files(data["links"].keys()) +        else: +            self.connector.proxy.abort_files([id]) +     +    def slotStopAllDownloads(self): +        """ +            emitted from main window +            stop all running downloads +        """ +        self.connector.stopAllDownloads() +     +    def slotClipboardChange(self): +        """ +            called if clipboard changes +        """ +        if self.checkClipboard: +            text = self.clipboard.text() +            pattern = re.compile(r"(http|https)://[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?/.*)?") +            matches = pattern.finditer(text) +            for match in matches: +                self.slotAddLinks([str(match.group(0))]) +     +    def slotSetClipboardStatus(self, status): +        """ +            set clipboard checking +        """ +        self.checkClipboard = status +     +    def slotChangePackageName(self, pid, name): +        """ +            package name edit finished +        """ +        self.connector.setPackageName(pid, str(name)) +     +    def slotPullOutPackage(self, pid): +        """ +            pull package out of the queue +        """ +        self.connector.proxy.pull_out_package(pid) +     +    def slotSetPriority(self, pid, level): +        """ +            set package priority +        """ +        self.connector.proxy.set_priority(pid, level) +     +    def checkCaptcha(self): +        if self.connector.captchaWaiting() and self.mainWindow.captchaDock.isFree(): +            cid, img, imgType = self.connector.getCaptcha() +            self.mainWindow.captchaDock.emit(SIGNAL("setTask"), cid, str(img), imgType) +            self.mainWindow.show() +        elif not self.mainWindow.captchaDock.isFree(): +            status = self.connector.getCaptchaStatus(self.mainWindow.captchaDock.currentID) +            if not (status == "user" or status == "shared-user"): +                self.mainWindow.captchaDock.hide() +                self.mainWindow.captchaDock.processing = False +                self.mainWindow.captchaDock.currentID = None +     +    def slotCaptchaDone(self, cid, result): +        self.connector.setCaptchaResult(str(cid), str(result)) +     +    def pullEvents(self): +        events = self.connector.getEvents() +        for event in events: +            if event[1] == "queue": +                self.queue.addEvent(event) +                try: +                    if event[0] == "update" and event[2] == "file": +                        info = self.connector.getLinkInfo(event[3]) +                        if info["status_type"] == "finished": +                            self.emit(SIGNAL("showMessage"), _("Finished downloading of '%s'") % info["status_filename"]) +                        elif info["status_type"] == "downloading": +                            self.emit(SIGNAL("showMessage"), _("Started downloading '%s'") % info["status_filename"]) +                        elif info["status_type"] == "failed": +                            self.emit(SIGNAL("showMessage"), _("Failed downloading '%s'!") % info["status_filename"]) +                    if event[0] == "insert" and event[2] == "file": +                        info = self.connector.getLinkInfo(event[3]) +                        self.emit(SIGNAL("showMessage"), _("Added '%s' to queue") % info["status_filename"]) +                except: +                    pass +            elif event[1] == "collector": +                self.packageCollector.addEvent(event) +     +    def slotReloadAccounts(self): +        self.mainWindow.tabs["accounts"]["view"].model().reloadData() +     +    class Loop(): +        def __init__(self, parent): +            self.parent = parent +            self.timer = QTimer() +            self.timer.connect(self.timer, SIGNAL("timeout()"), self.update) +         +        def start(self): +            self.update() +            self.timer.start(1000) +         +        def update(self): +            """ +                methods to call +            """ +            self.parent.refreshServerStatus() +            self.parent.refreshLog() +            self.parent.checkCaptcha() +            self.parent.pullEvents() +         +        def stop(self): +            self.timer.stop() +                     + +class TrayIcon(QSystemTrayIcon): +    def __init__(self): +        QSystemTrayIcon.__init__(self, QIcon(join("icons", "logo.png"))) +        self.contextMenu = QMenu() +        self.showAction = QAction(_("Show"), self.contextMenu) +        self.showAction.setCheckable(True) +        self.showAction.setChecked(True) +        self.showAction.setDisabled(True) +        self.contextMenu.addAction(self.showAction) +        self.exitAction = QAction(QIcon(join("icons", "close.png")), _("Exit"), self.contextMenu) +        self.contextMenu.addAction(self.exitAction) +        self.setContextMenu(self.contextMenu) +         +        self.connect(self, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.doubleClicked) +     +    def mainWindowHidden(self): +        self.showAction.setChecked(False) +     +    def doubleClicked(self, reason): +        if self.showAction.isEnabled(): +            if reason == QSystemTrayIcon.DoubleClick: +                self.showAction.toggle() + +class Notification(QObject): +    def __init__(self, tray): +        QObject.__init__(self) +        self.tray = tray +        self.usePynotify = False +         +        try: +            self.usePynotify = pynotify.init("icon-summary-body") +        except: +            pass +     +    def showMessage(self, body): +        if self.usePynotify: +            n = pynotify.Notification("pyload", body, join("icons", "logo.png")) +            try: +                n.set_hint_string("x-canonical-append", "") +            except: +                pass +            n.show() +        else: +            self.tray.showMessage("pyload", body) + +if __name__ == "__main__": +    app = main() +    app.loop() + | 
