diff options
Diffstat (limited to 'module/gui')
-rw-r--r-- | module/gui/CaptchaDock.py | 85 | ||||
-rw-r--r-- | module/gui/Collector.py | 312 | ||||
-rw-r--r-- | module/gui/ConnectionManager.py | 249 | ||||
-rw-r--r-- | module/gui/LinkDock.py | 54 | ||||
-rw-r--r-- | module/gui/MainWindow.py | 421 | ||||
-rw-r--r-- | module/gui/PWInputWindow.py | 47 | ||||
-rw-r--r-- | module/gui/PackageDock.py | 76 | ||||
-rw-r--r-- | module/gui/Queue.py | 373 | ||||
-rw-r--r-- | module/gui/XMLParser.py | 71 | ||||
-rw-r--r-- | module/gui/__init__.py | 1 | ||||
-rw-r--r-- | module/gui/connector.py | 424 |
11 files changed, 2113 insertions, 0 deletions
diff --git a/module/gui/CaptchaDock.py b/module/gui/CaptchaDock.py new file mode 100644 index 000000000..8a7e8010e --- /dev/null +++ b/module/gui/CaptchaDock.py @@ -0,0 +1,85 @@ +# -*- 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 +""" + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +class CaptchaDock(QDockWidget): + def __init__(self): + QDockWidget.__init__(self, "Captcha") + self.setObjectName("Captcha Dock") + self.widget = CaptchaDockWidget(self) + self.setWidget(self.widget) + self.setAllowedAreas(Qt.BottomDockWidgetArea) + self.setFeatures(QDockWidget.NoDockWidgetFeatures) + self.hide() + self.processing = False + self.currentID = None + self.connect(self, SIGNAL("setTask"), self.setTask) + + def isFree(self): + return not self.processing + + def setTask(self, tid, img, imgType): + self.processing = True + data = QByteArray(img) + self.currentID = tid + self.widget.emit(SIGNAL("setImage"), data) + self.widget.input.setText("") + self.show() + +class CaptchaDockWidget(QWidget): + def __init__(self, dock): + QWidget.__init__(self) + self.dock = dock + self.setLayout(QHBoxLayout()) + layout = self.layout() + + imgLabel = QLabel() + captchaInput = QLineEdit() + okayButton = QPushButton("OK") + cancelButton = QPushButton("Cancel") + + layout.addStretch() + layout.addWidget(imgLabel) + layout.addWidget(captchaInput) + layout.addWidget(okayButton) + layout.addWidget(cancelButton) + layout.addStretch() + + self.input = captchaInput + + self.connect(okayButton, SIGNAL("clicked()"), self.slotSubmit) + self.connect(captchaInput, SIGNAL("returnPressed()"), self.slotSubmit) + self.connect(self, SIGNAL("setImage"), self.setImg) + self.connect(self, SIGNAL("setPixmap(const QPixmap &)"), imgLabel, SLOT("setPixmap(const QPixmap &)")) + + def setImg(self, data): + pixmap = QPixmap() + pixmap.loadFromData(data) + self.emit(SIGNAL("setPixmap(const QPixmap &)"), pixmap) + + def slotSubmit(self): + text = self.input.text() + tid = self.dock.currentID + self.dock.currentID = None + self.dock.emit(SIGNAL("done"), tid, str(text)) + self.dock.hide() + self.dock.processing = False + diff --git a/module/gui/Collector.py b/module/gui/Collector.py new file mode 100644 index 000000000..0aad5d1ba --- /dev/null +++ b/module/gui/Collector.py @@ -0,0 +1,312 @@ +# -*- 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 +""" + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from module.gui.Queue import ItemIterator + +class PackageCollector(QObject): + def __init__(self, view, connector): + QObject.__init__(self) + self.view = view + self.connector = connector + self.collector = [] + self.interval = 2 + self.rootItem = self.view.invisibleRootItem() + self.mutex = QMutex() + item = self.PackageCollectorPack(self) + item.setPackData({"id":"fixed"}) + item.setData(0, Qt.DisplayRole, QVariant("Single Links")) + item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + self.rootItem.addChild(item) + self.linkCollector = item + self.pauseIDs = [] + self.updater = self.CollectorUpdater(self.interval) + self.connect(self.updater, SIGNAL("update()"), self.update) + + class CollectorUpdater(QThread): + def __init__(self, interval): + QThread.__init__(self) + self.interval = interval + self.running = True + + def run(self): + while self.running: + self.emit(SIGNAL("update()")) + self.sleep(self.interval) + + def start(self): + self.updater.start() + + def wait(self): + self.updater.wait() + + def stop(self): + self.updater.running = False + + def update(self): + locker = QMutexLocker(self.mutex) + packs = self.connector.getPackageCollector() + ids = [] + for data in packs: + ids.append(data["id"]) + self.clear(ids) + for data in packs: + if data["id"] in self.pauseIDs: + continue + ids.append(data["id"]) + pack = self.getPack(data["id"]) + if not pack: + pack = self.PackageCollectorPack(self) + pack.setPackData(data) + self.addPack(data["id"], pack) + files = self.connector.getPackageFiles(data["id"]) + pack.clear(files) + for fid in files: + info = self.connector.getLinkInfo(fid) + child = pack.getChild(fid) + if not child: + child = self.PackageCollectorFile(self, pack) + child.setFileData(info) + pack.addPackChild(fid, child) + + def addPack(self, pid, newPack): + pos = None + try: + for pack in ItemIterator(self.rootItem): + if pack.getPackData()["id"] == pid: + pos = self.rootItem.indexOfChild(pack) + break + if pos == None: + raise Exception() + item = self.rootItem.child(pos) + item.setPackData(newPack.getPackData()) + except: + self.rootItem.insertChild(self.rootItem.childCount()-1, newPack) + item = newPack + item.setData(0, Qt.DisplayRole, QVariant(item.getPackData()["package_name"])) + item.setData(0, Qt.UserRole, QVariant(pid)) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDropEnabled | Qt.ItemIsEditable) + + def getPack(self, pid): + for k, pack in enumerate(ItemIterator(self.rootItem)): + if pack.getPackData()["id"] == pid: + return pack + return None + + def clear(self, ids): + clear = False + remove = [] + for k, pack in enumerate(ItemIterator(self.rootItem)): + if not pack.getPackData()["id"] in ids and not pack.getPackData()["id"] == "fixed": + clear = True + remove.append(k) + if not clear: + return + remove.sort() + remove.reverse() + for k in remove: + self.rootItem.takeChild(k) + for pack in ItemIterator(self.rootItem): + if pack.getPackData()["id"] == "fixed": + return + item = self.PackageCollectorPack(self) + item.setPackData({"id":"fixed"}) + item.setData(0, Qt.DisplayRole, QVariant("Single Links")) + item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + self.rootItem.addChild(item) + self.linkCollector = item + + def pauseItemUpdate(self, pid, pause=True): + locker = QMutexLocker(self.mutex) + if pause and not pid in self.pauseIDs: + self.pauseIDs.append(int(pid)) + else: + try: + self.pauseIDs.remove(int(pid)) + except: + pass + + class PackageCollectorPack(QTreeWidgetItem): + def __init__(self, collector): + QTreeWidgetItem.__init__(self) + self.collector = collector + self._data = {} + + def addPackChild(self, cid, newChild): + pos = None + try: + for child in ItemIterator(self): + if child.getData()["id"] == cid: + pos = self.indexOfChild(child) + break + if pos == None: + raise Exception() + item = self.child(pos) + item.setFileData(newChild.getFileData()) + except: + self.addChild(newChild) + item = newChild + item.setData(0, Qt.DisplayRole, QVariant(item.getFileData()["filename"])) + item.setData(0, Qt.UserRole, QVariant(cid)) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + + def setPackData(self, data): + self._data = data + + def getPackData(self): + return self._data + + def getChildren(self): + ret = [] + for item in ItemIterator(self): + ret.append(item) + return ret + + def getChild(self, cid): + for item in ItemIterator(self): + if item.getFileData()["id"] == cid: + return item + return None + + def clear(self, ids): + clear = False + remove = [] + children = [] + for k, file in enumerate(self.getChildren()): + if not file.getFileData()["id"] in ids: + remove.append(file.getFileData()["id"]) + if file.getFileData()["id"] in children and not file.getFileData()["id"] in remove: + remove.append(file.getFileData()["id"]) + continue + children.append(file.getFileData()["id"]) + if not remove: + return + remove.sort() + remove.reverse() + parent = self + for k in remove: + parent.takeChild(k) + + class PackageCollectorFile(QTreeWidgetItem): + def __init__(self, collector, pack): + QTreeWidgetItem.__init__(self) + self.collector = collector + self.pack = pack + self._data = {} + self.wait_since = None + + def getFileData(self): + return self._data + + def setFileData(self, data): + self._data = data + + def getPack(self): + return self.pack + +class LinkCollector(QObject): + def __init__(self, view, root, connector): + QObject.__init__(self) + self.view = view + self.connector = connector + self.interval = 2 + self.rootItem = root + self.mutex = QMutex() + self.updater = self.CollectorUpdater(self.interval) + self.connect(self.updater, SIGNAL("update()"), self.update) + + class CollectorUpdater(QThread): + def __init__(self, interval): + QThread.__init__(self) + self.interval = interval + self.running = True + + def run(self): + while self.running: + self.emit(SIGNAL("update()")) + self.sleep(self.interval) + + def start(self): + self.updater.start() + + def wait(self): + self.updater.wait() + + def stop(self): + self.updater.running = False + + def update(self): + locker = QMutexLocker(self.mutex) + ids = self.connector.getLinkCollector() + self.clear(ids) + for fid in ids: + data = self.connector.getLinkInfo(fid) + file = self.getFile(fid) + if not file: + file = self.LinkCollectorFile(self) + file.setFileData(data) + self.addFile(fid, file) + + def addFile(self, pid, newFile): + pos = None + try: + for pack in ItemIterator(self.rootItem): + if file.getFileData()["id"] == pid: + pos = self.rootItem.indexOfChild(file) + break + if pos == None: + raise Exception() + item = self.rootItem.child(pos) + item.setFileData(newFile.getPackData()) + except: + self.rootItem.addChild(newFile) + item = newFile + item.setData(0, Qt.DisplayRole, QVariant(newFile.getFileData()["filename"])) + item.setData(0, Qt.UserRole, QVariant(pid)) + flags = Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled + item.setFlags(flags) + + def getFile(self, pid): + for file in ItemIterator(self.rootItem): + if file.getFileData()["id"] == pid: + return file + return None + + def clear(self, ids): + clear = False + for file in ItemIterator(self.rootItem): + if not file.getFileData()["id"] in ids: + clear = True + break + if not clear: + return + self.rootItem.takeChildren() + + class LinkCollectorFile(QTreeWidgetItem): + def __init__(self, collector): + QTreeWidgetItem.__init__(self) + self.collector = collector + self._data = {} + + def getFileData(self): + return self._data + + def setFileData(self, data): + self._data = data diff --git a/module/gui/ConnectionManager.py b/module/gui/ConnectionManager.py new file mode 100644 index 000000000..57aed3a18 --- /dev/null +++ b/module/gui/ConnectionManager.py @@ -0,0 +1,249 @@ +# -*- 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 +""" + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from uuid import uuid4 as uuid + +class ConnectionManager(QWidget): + def __init__(self): + QWidget.__init__(self) + + mainLayout = QHBoxLayout() + buttonLayout = QVBoxLayout() + + connList = QListWidget() + + new = QPushButton("New") + edit = QPushButton("Edit") + remove = QPushButton("Remove") + connect = QPushButton("Connect") + + mainLayout.addWidget(connList) + mainLayout.addLayout(buttonLayout) + + buttonLayout.addWidget(new) + buttonLayout.addWidget(edit) + buttonLayout.addWidget(remove) + buttonLayout.addStretch() + buttonLayout.addWidget(connect) + + self.setLayout(mainLayout) + + self.new = new + self.connectb = connect + self.remove = remove + self.editb = edit + self.connList = connList + self.edit = self.EditWindow() + self.connectSignals() + + self.defaultStates = {} + + def connectSignals(self): + self.connect(self, SIGNAL("setConnections(connections)"), self.setConnections) + self.connect(self.new, SIGNAL("clicked()"), self.slotNew) + self.connect(self.editb, SIGNAL("clicked()"), self.slotEdit) + self.connect(self.remove, SIGNAL("clicked()"), self.slotRemove) + self.connect(self.connectb, SIGNAL("clicked()"), self.slotConnect) + self.connect(self.edit, SIGNAL("save"), self.slotSave) + self.connect(self.connList, SIGNAL("itemDoubleClicked(QListWidgetItem *)"), self.slotItemDoubleClicked) + + def setConnections(self, connections): + self.connList.clear() + for conn in connections: + item = QListWidgetItem() + item.setData(Qt.DisplayRole, QVariant(conn["name"])) + item.setData(Qt.UserRole, QVariant(conn)) + self.connList.addItem(item) + if conn["default"]: + item.setData(Qt.DisplayRole, QVariant("%s (Default)" % conn["name"])) + self.connList.setCurrentItem(item) + + def slotNew(self): + data = {"id":uuid().hex, "type":"remote", "default":False, "name":"", "host":"", "ssl":False, "port":"7227", "user":"admin"} + self.edit.setData(data) + self.edit.show() + + def slotEdit(self): + item = self.connList.currentItem() + data = item.data(Qt.UserRole).toPyObject() + data = self.cleanDict(data) + self.edit.setData(data) + self.edit.show() + + def slotRemove(self): + item = self.connList.currentItem() + data = item.data(Qt.UserRole).toPyObject() + data = self.cleanDict(data) + self.emit(SIGNAL("removeConnection"), data) + + def slotConnect(self): + item = self.connList.currentItem() + data = item.data(Qt.UserRole).toPyObject() + data = self.cleanDict(data) + self.emit(SIGNAL("connect"), data) + + def cleanDict(self, data): + tmp = {} + for k, d in data.items(): + tmp[str(k)] = d + return tmp + + def slotSave(self, data): + self.emit(SIGNAL("saveConnection"), data) + + def slotItemDoubleClicked(self, defaultItem): + data = defaultItem.data(Qt.UserRole).toPyObject() + self.setDefault(data, True) + did = self.cleanDict(data)["id"] + allItems = self.connList.findItems("*", Qt.MatchWildcard) + count = self.connList.count() + for i in range(count): + item = self.connList.item(i) + data = item.data(Qt.UserRole).toPyObject() + if self.cleanDict(data)["id"] == did: + continue + self.setDefault(data, False) + + def setDefault(self, data, state): + data = self.cleanDict(data) + self.edit.setData(data) + data = self.edit.getData() + data["default"] = state + self.edit.emit(SIGNAL("save"), data) + + class EditWindow(QWidget): + def __init__(self): + QWidget.__init__(self) + + grid = QGridLayout() + + nameLabel = QLabel("Name:") + hostLabel = QLabel("Host:") + sslLabel = QLabel("SSL:") + localLabel = QLabel("Local:") + userLabel = QLabel("User:") + portLabel = QLabel("Port:") + + name = QLineEdit() + host = QLineEdit() + ssl = QCheckBox() + local = QCheckBox() + user = QLineEdit() + port = QSpinBox() + port.setRange(1,10000) + + save = QPushButton("Save") + cancel = QPushButton("Cancel") + + grid.addWidget(nameLabel, 0, 0) + grid.addWidget(name, 0, 1) + grid.addWidget(localLabel, 1, 0) + grid.addWidget(local, 1, 1) + grid.addWidget(hostLabel, 2, 0) + grid.addWidget(host, 2, 1) + grid.addWidget(sslLabel, 4, 0) + grid.addWidget(ssl, 4, 1) + grid.addWidget(userLabel, 5, 0) + grid.addWidget(user, 5, 1) + grid.addWidget(portLabel, 3, 0) + grid.addWidget(port, 3, 1) + grid.addWidget(cancel, 6, 0) + grid.addWidget(save, 6, 1) + + self.setLayout(grid) + self.controls = {} + self.controls["name"] = name + self.controls["host"] = host + self.controls["ssl"] = ssl + self.controls["local"] = local + self.controls["user"] = user + self.controls["port"] = port + self.controls["save"] = save + self.controls["cancel"] = cancel + + self.connect(cancel, SIGNAL("clicked()"), self.hide) + self.connect(save, SIGNAL("clicked()"), self.slotDone) + self.connect(local, SIGNAL("stateChanged(int)"), self.slotLocalChanged) + + self.id = None + self.default = None + + def setData(self, data): + self.id = data["id"] + self.default = data["default"] + self.controls["name"].setText(data["name"]) + if data["type"] == "local": + data["local"] = True + else: + data["local"] = False + self.controls["local"].setChecked(data["local"]) + if not data["local"]: + self.controls["ssl"].setChecked(data["ssl"]) + self.controls["user"].setText(data["user"]) + self.controls["port"].setValue(int(data["port"])) + self.controls["host"].setText(data["host"]) + self.controls["ssl"].setDisabled(False) + self.controls["user"].setDisabled(False) + self.controls["port"].setDisabled(False) + self.controls["host"].setDisabled(False) + else: + self.controls["ssl"].setChecked(False) + self.controls["user"].setText("") + self.controls["port"].setValue(1) + self.controls["host"].setText("") + self.controls["ssl"].setDisabled(True) + self.controls["user"].setDisabled(True) + self.controls["port"].setDisabled(True) + self.controls["host"].setDisabled(True) + + def slotLocalChanged(self, val): + if val == 2: + self.controls["ssl"].setDisabled(True) + self.controls["user"].setDisabled(True) + self.controls["port"].setDisabled(True) + self.controls["host"].setDisabled(True) + elif val == 0: + self.controls["ssl"].setDisabled(False) + self.controls["user"].setDisabled(False) + self.controls["port"].setDisabled(False) + self.controls["host"].setDisabled(False) + + def getData(self): + d = {} + d["id"] = self.id + d["default"] = self.default + d["name"] = self.controls["name"].text() + d["local"] = self.controls["local"].isChecked() + d["ssl"] = str(self.controls["ssl"].isChecked()) + d["user"] = self.controls["user"].text() + d["host"] = self.controls["host"].text() + d["port"] = self.controls["port"].value() + if d["local"]: + d["type"] = "local" + else: + d["type"] = "remote" + return d + + def slotDone(self): + data = self.getData() + self.hide() + self.emit(SIGNAL("save"), data) + diff --git a/module/gui/LinkDock.py b/module/gui/LinkDock.py new file mode 100644 index 000000000..99429d04b --- /dev/null +++ b/module/gui/LinkDock.py @@ -0,0 +1,54 @@ +# -*- 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 +""" + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +class NewLinkDock(QDockWidget): + def __init__(self): + QDockWidget.__init__(self, "New Links") + self.setObjectName("New Links Dock") + self.widget = NewLinkWindow(self) + self.setWidget(self.widget) + self.setAllowedAreas(Qt.RightDockWidgetArea|Qt.LeftDockWidgetArea) + self.hide() + + def slotDone(self): + text = str(self.widget.box.toPlainText()) + lines = text.splitlines() + self.emit(SIGNAL("done"), lines) + self.widget.box.clear() + self.hide() + +class NewLinkWindow(QWidget): + def __init__(self, dock): + QWidget.__init__(self) + self.dock = dock + self.setLayout(QVBoxLayout()) + layout = self.layout() + + boxLabel = QLabel("Paste URLs here:") + self.box = QTextEdit() + + save = QPushButton("Add") + + layout.addWidget(boxLabel) + layout.addWidget(self.box) + layout.addWidget(save) + + self.connect(save, SIGNAL("clicked()"), self.dock.slotDone) diff --git a/module/gui/MainWindow.py b/module/gui/MainWindow.py new file mode 100644 index 000000000..250b701e4 --- /dev/null +++ b/module/gui/MainWindow.py @@ -0,0 +1,421 @@ +# -*- 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 +""" + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from module.gui.PackageDock import * +from module.gui.LinkDock import * +from module.gui.CaptchaDock import CaptchaDock + +class MainWindow(QMainWindow): + def __init__(self): + """ + set up main window + """ + QMainWindow.__init__(self) + #window stuff + self.setWindowTitle("pyLoad Client") + self.setWindowIcon(QIcon("icons/logo.png")) + self.resize(850,500) + + #layout version + self.version = 2 + + #init docks + self.newPackDock = NewPackageDock() + self.addDockWidget(Qt.RightDockWidgetArea, self.newPackDock) + self.newLinkDock = NewLinkDock() + self.addDockWidget(Qt.RightDockWidgetArea, self.newLinkDock) + self.connect(self.newLinkDock, SIGNAL("done"), self.slotAddLinks) + self.connect(self.newPackDock, SIGNAL("done"), self.slotAddPackage) + self.captchaDock = CaptchaDock() + self.addDockWidget(Qt.BottomDockWidgetArea, self.captchaDock) + + #central widget, layout + self.masterlayout = QVBoxLayout() + lw = QWidget() + lw.setLayout(self.masterlayout) + self.setCentralWidget(lw) + + #set menubar and statusbar + self.menubar = self.menuBar() + self.statusbar = self.statusBar() + self.connect(self.statusbar, SIGNAL("showMsg"), self.statusbar.showMessage) + self.serverStatus = QLabel("Status: Not Connected") + self.statusbar.addPermanentWidget(self.serverStatus) + + #menu + self.menus = {} + self.menus["file"] = self.menubar.addMenu("&File") + self.menus["connections"] = self.menubar.addMenu("&Connections") + + #menu actions + self.mactions = {} + self.mactions["exit"] = QAction("Exit", self.menus["file"]) + self.mactions["manager"] = QAction("Connection manager", self.menus["connections"]) + + #add menu actions + self.menus["file"].addAction(self.mactions["exit"]) + self.menus["connections"].addAction(self.mactions["manager"]) + + #toolbar + self.actions = {} + self.init_toolbar() + + #tabs + self.tabw = QTabWidget() + self.tabs = {} + self.tabs["queue"] = {"w":QWidget()} + self.tabs["collector"] = {"w":QWidget()} + self.tabs["settings"] = {"w":QWidget()} + self.tabs["log"] = {"w":QWidget()} + self.tabw.addTab(self.tabs["queue"]["w"], "Queue") + self.tabw.addTab(self.tabs["collector"]["w"], "Collector") + self.tabw.addTab(self.tabs["settings"]["w"], "Settings") + self.tabw.addTab(self.tabs["log"]["w"], "Log") + self.tabw.setTabEnabled(2, False) + + #init tabs + self.init_tabs() + + #context menus + self.init_context() + + #layout + self.masterlayout.addWidget(self.tabw) + + #signals.. + self.connect(self.mactions["manager"], SIGNAL("triggered()"), self.slotShowConnector) + self.connect(self.mactions["exit"], SIGNAL("triggered()"), self.close) + + self.connect(self.tabs["queue"]["view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotQueueContextMenu) + self.connect(self.tabs["collector"]["package_view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotCollectorContextMenu) + self.connect(self.tabs["collector"]["package_view"].itemDelegate(), SIGNAL("closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint)"), self.slotCloseItemEditor) + + self.lastAddedID = None + + def init_toolbar(self): + """ + create toolbar + """ + self.toolbar = self.addToolBar("Main Toolbar") + self.toolbar.setObjectName("Main Toolbar") + self.toolbar.setIconSize(QSize(40,40)) + self.actions["toggle_status"] = self.toolbar.addAction("Toggle Pause/Resume") + pricon = QIcon() + pricon.addFile("icons/gui/toolbar_start.png", QSize(), QIcon.Normal, QIcon.Off) + pricon.addFile("icons/gui/toolbar_pause.png", QSize(), QIcon.Normal, QIcon.On) + self.actions["toggle_status"].setIcon(pricon) + self.actions["toggle_status"].setCheckable(True) + self.actions["status_stop"] = self.toolbar.addAction(QIcon("icons/gui/toolbar_stop.png"), "Stop") + self.toolbar.addSeparator() + self.actions["add"] = self.toolbar.addAction(QIcon("icons/gui/toolbar_add.png"), "Add") + self.toolbar.addSeparator() + self.actions["clipboard"] = self.toolbar.addAction(QIcon("icons/gui/clipboard.png"), "Check Clipboard") + self.actions["clipboard"].setCheckable(True) + + self.connect(self.actions["toggle_status"], SIGNAL("toggled(bool)"), self.slotToggleStatus) + self.connect(self.actions["clipboard"], SIGNAL("toggled(bool)"), self.slotToggleClipboard) + self.connect(self.actions["status_stop"], SIGNAL("triggered()"), self.slotStatusStop) + self.addMenu = QMenu() + packageAction = self.addMenu.addAction("Package") + linkAction = self.addMenu.addAction("Links") + containerAction = self.addMenu.addAction("Container") + self.connect(self.actions["add"], SIGNAL("triggered()"), self.slotAdd) + self.connect(packageAction, SIGNAL("triggered()"), self.slotShowAddPackage) + self.connect(linkAction, SIGNAL("triggered()"), self.slotShowAddLinks) + self.connect(containerAction, SIGNAL("triggered()"), self.slotShowAddContainer) + + def init_tabs(self): + """ + create tabs + """ + #queue + self.tabs["queue"]["l"] = QGridLayout() + self.tabs["queue"]["w"].setLayout(self.tabs["queue"]["l"]) + self.tabs["queue"]["view"] = QTreeWidget() + self.tabs["queue"]["l"].addWidget(self.tabs["queue"]["view"]) + + #collector + groupPackage = QGroupBox("Packages") + groupPackage.setLayout(QVBoxLayout()) + toQueue = QPushButton("Push selected packages to queue") + self.tabs["collector"]["l"] = QGridLayout() + self.tabs["collector"]["w"].setLayout(self.tabs["collector"]["l"]) + self.tabs["collector"]["package_view"] = QTreeWidget() + groupPackage.layout().addWidget(self.tabs["collector"]["package_view"]) + groupPackage.layout().addWidget(toQueue) + self.tabs["collector"]["l"].addWidget(groupPackage, 0, 0) + self.connect(toQueue, SIGNAL("clicked()"), self.slotPushPackageToQueue) + self.tabs["collector"]["package_view"].setContextMenuPolicy(Qt.CustomContextMenu) + self.tabs["queue"]["view"].setContextMenuPolicy(Qt.CustomContextMenu) + + #settings + self.tabs["settings"]["l"] = QGridLayout() + self.tabs["settings"]["w"].setLayout(self.tabs["settings"]["l"]) + + #log + self.tabs["log"]["l"] = QGridLayout() + self.tabs["log"]["w"].setLayout(self.tabs["log"]["l"]) + self.tabs["log"]["text"] = QTextEdit() + self.tabs["log"]["text"].logOffset = 0 + self.tabs["log"]["text"].setReadOnly(True) + self.connect(self.tabs["log"]["text"], SIGNAL("append(QString)"), self.tabs["log"]["text"].append) + self.tabs["log"]["l"].addWidget(self.tabs["log"]["text"]) + + def init_context(self): + """ + create context menus + """ + self.activeMenu = None + #queue + self.queueContext = QMenu() + self.queueContext.buttons = {} + self.queueContext.item = (None, None) + self.queueContext.buttons["remove"] = QAction(QIcon("icons/gui/remove_small.png"), "Remove", self.queueContext) + self.queueContext.buttons["restart"] = QAction(QIcon("icons/gui/refresh_small.png"), "Restart", self.queueContext) + self.queueContext.buttons["pull"] = QAction(QIcon("icons/gui/pull_small.png"), "Pull out", self.queueContext) + self.queueContext.addAction(self.queueContext.buttons["pull"]) + self.queueContext.addAction(self.queueContext.buttons["remove"]) + self.queueContext.addAction(self.queueContext.buttons["restart"]) + self.connect(self.queueContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveDownload) + self.connect(self.queueContext.buttons["restart"], SIGNAL("triggered()"), self.slotRestartDownload) + self.connect(self.queueContext.buttons["pull"], SIGNAL("triggered()"), self.slotPullOutPackage) + + #collector + self.collectorContext = QMenu() + self.collectorContext.buttons = {} + self.collectorContext.item = (None, None) + self.collectorContext.buttons["remove"] = QAction(QIcon("icons/gui/remove_small.png"), "Remove", self.collectorContext) + self.collectorContext.buttons["push"] = QAction(QIcon("icons/gui/push_small.png"), "Push to queue", self.collectorContext) + self.collectorContext.buttons["edit"] = QAction(QIcon("icons/gui/edit_small.png"), "Edit Name", self.collectorContext) + self.collectorContext.addAction(self.collectorContext.buttons["push"]) + self.collectorContext.addAction(self.collectorContext.buttons["edit"]) + self.collectorContext.addAction(self.collectorContext.buttons["remove"]) + self.connect(self.collectorContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveDownload) + self.connect(self.collectorContext.buttons["push"], SIGNAL("triggered()"), self.slotPushPackageToQueue) + self.connect(self.collectorContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditPackage) + + def slotToggleStatus(self, status): + """ + pause/start toggle (toolbar) + """ + self.emit(SIGNAL("setDownloadStatus"), status) + + def slotStatusStop(self): + """ + stop button (toolbar) + """ + self.emit(SIGNAL("stopAllDownloads")) + + def slotAdd(self): + """ + add button (toolbar) + show context menu (choice: links/package) + """ + self.addMenu.exec_(QCursor.pos()) + + def slotShowAddPackage(self): + """ + action from add-menu + show new-package dock + """ + self.tabw.setCurrentIndex(1) + self.newPackDock.show() + + def slotShowAddLinks(self): + """ + action from add-menu + show new-links dock + """ + self.tabw.setCurrentIndex(1) + self.newLinkDock.show() + + def slotShowConnector(self): + """ + connectionmanager action triggered + let main to the stuff + """ + self.emit(SIGNAL("connector")) + + def slotAddLinks(self, links): + """ + new links + let main to the stuff + """ + self.emit(SIGNAL("addLinks"), links) + + def slotAddPackage(self, name, ids): + """ + new package + let main to the stuff + """ + self.emit(SIGNAL("addPackage"), name, ids) + + def slotShowAddContainer(self): + """ + action from add-menu + show file selector, emit upload + """ + fileNames = QFileDialog.getOpenFileNames(self, "Container Öffnen", "", "All Container Types (*.dlc *.ccf *.rsdf *.txt);;DLC (*.dlc);;CCF (*.ccf);;RSDF (*.rsdf);;Text Files (*.txt)") + for name in fileNames: + self.emit(SIGNAL("addContainer"), str(name)) + + def slotPushPackageToQueue(self): + """ + push collector pack to queue + get child ids + let main to the rest + """ + items = self.tabs["collector"]["package_view"].selectedItems() + for item in items: + try: + item.getFileData() + id = item.parent().getPackData()["id"] + pack = item.parent() + except: + id = item.getPackData()["id"] + pack = item + if id == "fixed": + ids = [] + for child in pack.getChildren(): + ids.append(child.getFileData()["id"]) + self.emit(SIGNAL("addPackage"), "Single Links", ids) + id = self.lastAddedID + self.emit(SIGNAL("pushPackageToQueue"), id) + + def saveWindow(self): + """ + get window state/geometry + pass data to main + """ + state_raw = self.saveState(self.version) + geo_raw = self.saveGeometry() + + state = str(state_raw.toBase64()) + geo = str(geo_raw.toBase64()) + + self.emit(SIGNAL("saveMainWindow"), state, geo) + + def closeEvent(self, event): + """ + somebody wants to close me! + let me first save my state + """ + self.saveWindow() + event.accept() + + def restoreWindow(self, state, geo): + """ + restore window state/geometry + """ + state = QByteArray(state) + geo = QByteArray(geo) + + state_raw = QByteArray.fromBase64(state) + geo_raw = QByteArray.fromBase64(geo) + + self.restoreState(state_raw, self.version) + self.restoreGeometry(geo_raw) + + def slotQueueContextMenu(self, pos): + """ + custom context menu in queue view requested + """ + globalPos = self.tabs["queue"]["view"].mapToGlobal(pos) + i = self.tabs["queue"]["view"].itemAt(pos) + if not i: + return + i.setSelected(True) + self.queueContext.item = (i.data(0, Qt.UserRole).toPyObject(), i.parent() == None) + menuPos = QCursor.pos() + menuPos.setX(menuPos.x()+2) + self.activeMenu = self.queueContext + if hasattr(i, "getPackData"): + self.queueContext.buttons["pull"].setVisible(True) + else: + self.queueContext.buttons["pull"].setVisible(False) + self.queueContext.exec_(menuPos) + + def slotCollectorContextMenu(self, pos): + """ + custom context menu in package collector view requested + """ + globalPos = self.tabs["collector"]["package_view"].mapToGlobal(pos) + i = self.tabs["collector"]["package_view"].itemAt(pos) + if not i: + return + i.setSelected(True) + self.collectorContext.item = (i.data(0, Qt.UserRole).toPyObject(), i.parent() == None) + menuPos = QCursor.pos() + menuPos.setX(menuPos.x()+2) + self.activeMenu = self.collectorContext + if hasattr(i, "getPackData"): + self.collectorContext.buttons["edit"].setVisible(True) + else: + self.collectorContext.buttons["edit"].setVisible(False) + self.collectorContext.exec_(menuPos) + + def slotLinkCollectorContextMenu(self, pos): + """ + custom context menu in link collector view requested + """ + pass + + def slotRestartDownload(self): + """ + restart download action is triggered + """ + id, isTopLevel = self.queueContext.item + if not id == None: + self.emit(SIGNAL("restartDownload"), id, isTopLevel) + + def slotRemoveDownload(self): + """ + remove download action is triggered + """ + id, isTopLevel = self.activeMenu.item + if not id == None: + self.emit(SIGNAL("removeDownload"), id, isTopLevel) + + def slotToggleClipboard(self, status): + """ + check clipboard (toolbar) + """ + self.emit(SIGNAL("setClipboardStatus"), status) + + def slotEditPackage(self): + item = self.tabs["collector"]["package_view"].currentItem() + pid = self.tabs["collector"]["package_view"].currentItem().data(0, Qt.UserRole).toPyObject() + self.emit(SIGNAL("pauseItemUpdate"), pid, True) + self.tabs["collector"]["package_view"].editItem(item, 0) + + def slotCloseItemEditor(self, editor, hint): + pid = self.tabs["collector"]["package_view"].currentItem().data(0, Qt.UserRole).toPyObject() + self.emit(SIGNAL("changePackageName"), pid, editor.text()) + self.emit(SIGNAL("pauseItemUpdate"), pid, False) + + def slotPullOutPackage(self): + """ + pull package out of the queue + """ + id, isTopLevel = self.activeMenu.item + if not id == None: + self.emit(SIGNAL("pullOutPackage"), id, isTopLevel) + diff --git a/module/gui/PWInputWindow.py b/module/gui/PWInputWindow.py new file mode 100644 index 000000000..4fc834c4f --- /dev/null +++ b/module/gui/PWInputWindow.py @@ -0,0 +1,47 @@ +# -*- 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 +""" + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +class PWInputWindow(QWidget): + def __init__(self): + QWidget.__init__(self) + self.input = QLineEdit() + self.input.setEchoMode(QLineEdit.Password) + label = QLabel("Password:") + ok = QPushButton("OK") + cancel = QPushButton("Cancel") + grid = QGridLayout() + grid.addWidget(label, 0, 0, 1, 2) + grid.addWidget(self.input, 1, 0, 1, 2) + grid.addWidget(cancel, 2, 0) + grid.addWidget(ok, 2, 1) + self.setLayout(grid) + + self.connect(ok, SIGNAL("clicked()"), self.slotOK) + self.connect(cancel, SIGNAL("clicked()"), self.slotCancel) + self.connect(self.input, SIGNAL("returnPressed()"), self.slotOK) + + def slotOK(self): + self.hide() + self.emit(SIGNAL("ok"), self.input.text()) + + def slotCancel(self): + self.hide() + self.emit(SIGNAL("cancel")) diff --git a/module/gui/PackageDock.py b/module/gui/PackageDock.py new file mode 100644 index 000000000..c81798935 --- /dev/null +++ b/module/gui/PackageDock.py @@ -0,0 +1,76 @@ +# -*- 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 +""" + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +class NewPackageDock(QDockWidget): + def __init__(self): + QDockWidget.__init__(self, "New Package") + self.setObjectName("New Package Dock") + self.widget = NewPackageWindow(self) + self.setWidget(self.widget) + self.setAllowedAreas(Qt.RightDockWidgetArea|Qt.LeftDockWidgetArea) + self.hide() + + def slotDone(self): + view = self.widget.view + count = view.topLevelItemCount() + ids = [] + for i in range(count): + item = view.topLevelItem(i) + if item: + ids.append(item.data(0, Qt.UserRole).toInt()[0]) + self.emit(SIGNAL("done"), self.widget.nameInput.text(), ids) + view.clear() + self.hide() + +class NewPackageWindow(QWidget): + def __init__(self, dock): + QWidget.__init__(self) + self.dock = dock + self.setLayout(QGridLayout()) + layout = self.layout() + + nameLabel = QLabel("Name") + nameInput = QLineEdit() + + linksLabel = QLabel("Links in this Package") + linkView = QTreeWidget() + linkView.setSelectionBehavior(QAbstractItemView.SelectRows) + linkView.setSelectionMode(QAbstractItemView.ExtendedSelection) + linkView.setColumnCount(1) + linkView.setHeaderLabels(["Name"]) + linkView.setDragEnabled(True) + linkView.setDragDropMode(QAbstractItemView.DragDrop) + linkView.setDropIndicatorShown(True) + linkView.setAcceptDrops(True) + linkView.setDragDropOverwriteMode(True) + + self.view = linkView + self.nameInput = nameInput + + save = QPushButton("Create") + + layout.addWidget(nameLabel, 0, 0) + layout.addWidget(nameInput, 0, 1) + layout.addWidget(linksLabel, 1, 0, 1, 2) + layout.addWidget(linkView, 2, 0, 1, 2) + layout.addWidget(save, 3, 0, 1, 2) + + self.connect(save, SIGNAL("clicked()"), self.dock.slotDone) diff --git a/module/gui/Queue.py b/module/gui/Queue.py new file mode 100644 index 000000000..8681d3bb1 --- /dev/null +++ b/module/gui/Queue.py @@ -0,0 +1,373 @@ +# -*- 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 +""" + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from time import sleep, time + +class Queue(QObject): + def __init__(self, view, connector): + QObject.__init__(self) + self.view = view + self.connector = connector + self.statusMap = { + "finished": 0, + "queued": 1, + "checking": 2, + "waiting": 3, + "reconnected": 4, + "starting": 5, + "failed": 6, + "aborted": 7, + "decrypting": 8, + "downloading": 9 + } + self.statusMapReverse = dict((v,k) for k, v in self.statusMap.iteritems()) + self.interval = 1 + self.wait_dict = {} + self.rootItem = self.view.invisibleRootItem() + self.mutex = QMutex() + self.updater = self.QueueUpdater(self.interval) + self.connect(self.updater, SIGNAL("update()"), self.update) + + class QueueUpdater(QThread): + def __init__(self, interval): + QThread.__init__(self) + self.interval = interval + self.running = True + + def run(self): + while self.running: + self.emit(SIGNAL("update()")) + self.sleep(self.interval) + + def start(self): + self.updater.start() + + def wait(self): + self.updater.wait() + + def stop(self): + self.updater.running = False + + def update(self): + locker = QMutexLocker(self.mutex) + packs = self.connector.getPackageQueue() + downloading_raw = self.connector.getDownloadQueue() + downloading = {} + for d in downloading_raw: + did = d["id"] + del d["id"] + del d["name"] + del d["status"] + downloading[did] = d + ids = [] + for data in packs: + ids.append(data["id"]) + self.clear(ids) + for data in packs: + pack = self.getPack(data["id"]) + if not pack: + pack = self.QueuePack(self) + pack.setPackData(data) + files = self.connector.getPackageFiles(data["id"]) + pack.clear(files) + self.addPack(data["id"], pack) + for fid in files: + info = self.connector.getLinkInfo(fid) + child = pack.getChild(fid) + if not child: + child = self.QueueFile(self, pack) + info["downloading"] = None + try: + info["downloading"] = downloading[info["id"]] + except: + pass + if not info["status_type"]: + info["status_type"] = "queued" + child.setFileData(info) + pack.addPackChild(fid, child) + self.addPack(data["id"], pack) + + def addPack(self, pid, newPack): + pos = None + try: + for pack in QueueIterator(self.rootItem): + if pack.getPackData()["id"] == pid: + pos = self.rootItem.indexOfChild(pack) + break + if pos == None: + raise Exception() + item = self.rootItem.child(pos) + item.setPackData(newPack.getPackData()) + except: + self.rootItem.addChild(newPack) + item = newPack + item.setData(0, Qt.DisplayRole, QVariant(item.getPackData()["package_name"])) + status = -1 + speed = self.getSpeed(item) + plugins = [] + for child in item.getChildren(): + data = child.getFileData() + if self.statusMap.has_key(data["status_type"]) and self.statusMap[data["status_type"]] > status: + status = self.statusMap[data["status_type"]] + if not data["plugin"] in plugins: + plugins.append(data["plugin"]) + if status >= 0: + if speed == None or self.statusMapReverse[status] == "starting" or self.statusMapReverse[status] == "decrypting": + statustxt = self.statusMapReverse[status] + else: + statustxt = "%s (%s KB/s)" % (self.statusMapReverse[status], speed) + item.setData(2, Qt.DisplayRole, QVariant(statustxt)) + item.setData(1, Qt.DisplayRole, QVariant(", ".join(plugins))) + item.setData(0, Qt.UserRole, QVariant(pid)) + item.setData(3, Qt.UserRole, QVariant(item)) + + def getPack(self, pid): + for k, pack in enumerate(ItemIterator(self.rootItem)): + if pack.getPackData()["id"] == pid: + return pack + return None + + def clear(self, ids): + clear = False + remove = [] + for k, pack in enumerate(ItemIterator(self.rootItem)): + if not pack.getPackData()["id"] in ids: + clear = True + remove.append(k) + if not clear: + return + remove.sort() + remove.reverse() + for k in remove: + self.rootItem.takeChild(k) + + def getWaitingProgress(self, q): + locker = QMutexLocker(self.mutex) + if isinstance(q, self.QueueFile): + data = q.getFileData() + if data["status_type"] == "waiting" and data["downloading"]: + until = float(data["downloading"]["wait_until"]) + try: + since, until_old = self.wait_dict[data["id"]] + if not until == until_old: + raise Exception + except: + since = time() + self.wait_dict[data["id"]] = since, until + since = float(since) + max_wait = float(until-since) + rest = int(until-time()) + res = 100/max_wait + perc = rest*res + return perc, rest + return None + + def getProgress(self, q): + locker = QMutexLocker(self.mutex) + if isinstance(q, self.QueueFile): + data = q.getFileData() + if data["downloading"]: + return int(data["downloading"]["percent"]) + if data["status_type"] == "finished" or \ + data["status_type"] == "failed" or \ + data["status_type"] == "aborted": + return 100 + elif isinstance(q, self.QueuePack): + children = q.getChildren() + count = len(children) + perc_sum = 0 + for child in children: + val = 0 + data = child.getFileData() + if data["downloading"]: + val = int(data["downloading"]["percent"]) + elif data["status_type"] == "finished" or \ + data["status_type"] == "failed" or \ + data["status_type"] == "aborted": + val = 100 + perc_sum += val + if count == 0: + return 0 + return perc_sum/count + return 0 + + def getSpeed(self, q): + if isinstance(q, self.QueueFile): + data = q.getFileData() + if data["downloading"]: + return int(data["downloading"]["speed"]) + elif isinstance(q, self.QueuePack): + children = q.getChildren() + count = len(children) + speed_sum = 0 + all_waiting = True + running = False + for child in children: + val = 0 + data = child.getFileData() + if data["downloading"]: + if not data["status_type"] == "waiting": + all_waiting = False + val = int(data["downloading"]["speed"]) + running = True + speed_sum += val + if count == 0 or not running or all_waiting: + return None + return speed_sum + return None + + class QueuePack(QTreeWidgetItem): + def __init__(self, queue): + QTreeWidgetItem.__init__(self) + self.queue = queue + self._data = {} + + def addPackChild(self, cid, newChild): + pos = None + try: + for child in ItemIterator(self): + if child.getData()["id"] == cid: + pos = self.indexOfChild(child) + break + if pos == None: + raise Exception() + item = self.child(pos) + item.setFileData(newChild.getFileData()) + except: + self.addChild(newChild) + item = newChild + speed = self.queue.getSpeed(item) + if speed == None or item.getFileData()["status_type"] == "starting" or item.getFileData()["status_type"] == "decrypting": + status = item.getFileData()["status_type"] + else: + status = "%s (%s KB/s)" % (item.getFileData()["status_type"], speed) + item.setData(0, Qt.DisplayRole, QVariant(item.getFileData()["filename"])) + item.setData(2, Qt.DisplayRole, QVariant(status)) + item.setData(1, Qt.DisplayRole, QVariant(item.getFileData()["plugin"])) + item.setData(0, Qt.UserRole, QVariant(cid)) + item.setData(3, Qt.UserRole, QVariant(item)) + + def setPackData(self, data): + self._data = data + + def getPackData(self): + return self._data + + def getChildren(self): + ret = [] + for item in ItemIterator(self): + ret.append(item) + return ret + + def getChild(self, cid): + for item in ItemIterator(self): + if item.getFileData()["id"] == cid: + return item + return None + + def clear(self, ids): + clear = False + remove = [] + children = [] + for k, file in enumerate(self.getChildren()): + if not file.getFileData()["id"] in ids: + remove.append(file.getFileData()["id"]) + if file.getFileData()["id"] in children and not file.getFileData()["id"] in remove: + remove.append(file.getFileData()["id"]) + continue + children.append(file.getFileData()["id"]) + if not remove: + return + remove.sort() + remove.reverse() + for k in remove: + self.takeChild(k) + + class QueueFile(QTreeWidgetItem): + def __init__(self, queue, pack): + QTreeWidgetItem.__init__(self) + self.queue = queue + self.pack = pack + self._data = {} + self.wait_since = None + + def getFileData(self): + return self._data + + def setFileData(self, data): + self._data = data + + def getPack(self): + return self.pack + +class QueueProgressBarDelegate(QItemDelegate): + def __init__(self, parent, queue): + QItemDelegate.__init__(self, parent) + self.queue = queue + + def paint(self, painter, option, index): + if index.column() == 3: + qe = index.data(Qt.UserRole).toPyObject() + w = self.queue.getWaitingProgress(qe) + wait = None + if w: + progress = w[0] + wait = w[1] + else: + progress = self.queue.getProgress(qe) + opts = QStyleOptionProgressBarV2() + opts.maximum = 100 + opts.minimum = 0 + opts.progress = progress + opts.rect = option.rect + opts.rect.setRight(option.rect.right()-1) + opts.rect.setHeight(option.rect.height()-1) + opts.textVisible = True + opts.textAlignment = Qt.AlignCenter + if not wait == None: + opts.text = QString("waiting %d seconds" % (wait,)) + else: + opts.text = QString.number(opts.progress) + "%" + QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter) + return + QItemDelegate.paint(self, painter, option, index) + +class ItemIterator(): + def __init__(self, item): + self.item = item + self.current = -1 + + def __iadd__(self, val): + self.current += val + + def value(self): + return self.item.child(self.current) + + def next(self): + self.__iadd__(1) + value = self.value() + if value: + return self.value() + else: + raise StopIteration + + def __iter__(self): + return self diff --git a/module/gui/XMLParser.py b/module/gui/XMLParser.py new file mode 100644 index 000000000..5e3b7bf65 --- /dev/null +++ b/module/gui/XMLParser.py @@ -0,0 +1,71 @@ +# -*- 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 +""" +from __future__ import with_statement + +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from PyQt4.QtXml import * + +import os + +class XMLParser(): + def __init__(self, data, dfile=""): + self.mutex = QMutex() + self.mutex.lock() + self.xml = QDomDocument() + self.file = data + self.dfile = dfile + self.mutex.unlock() + self.loadData() + self.root = self.xml.documentElement() + + def loadData(self): + self.mutex.lock() + f = self.file + if not os.path.exists(f): + f = self.dfile + with open(f, 'r') as fh: + content = fh.read() + self.xml.setContent(content) + self.mutex.unlock() + + def saveData(self): + self.mutex.lock() + content = self.xml.toString() + with open(self.file, 'w') as fh: + fh.write(content) + self.mutex.unlock() + return content + + def parseNode(self, node, ret_type="list"): + if ret_type == "dict": + childNodes = {} + else: + childNodes = [] + child = node.firstChild() + while True: + n = child.toElement() + if n.isNull(): + break + else: + if ret_type == "dict": + childNodes[str(n.tagName())] = n + else: + childNodes.append(n) + child = child.nextSibling() + return childNodes diff --git a/module/gui/__init__.py b/module/gui/__init__.py new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/module/gui/__init__.py @@ -0,0 +1 @@ + diff --git a/module/gui/connector.py b/module/gui/connector.py new file mode 100644 index 000000000..432999161 --- /dev/null +++ b/module/gui/connector.py @@ -0,0 +1,424 @@ +# -*- 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 +""" + +SERVER_VERSION = "0.3" + +from time import sleep + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from xmlrpclib import ServerProxy + +class connector(QThread): + def __init__(self): + """ + init thread + """ + QThread.__init__(self) + self.mutex = QMutex() + self.running = True + self.proxy = None + self.addr = None + self.errorQueue = [] + self.connect(self, SIGNAL("proxy_error"), self._proxyError) + + def setAddr(self, addr): + """ + set new address + """ + self.mutex.lock() + self.addr = addr + self.mutex.unlock() + + def run(self): + """ + start thread + (called from thread.start()) + """ + self.canConnect() + while self.running: + sleep(1) + self.getError() + + def canConnect(self): + return self.connectProxy(self.addr) + + def stop(self): + """ + stop thread + """ + self.running = False + + def connectProxy(self, addr): + """ + connect to remote server + """ + self.proxy = ServerProxy(addr, allow_none=True) + try: + server_version = self.proxy.get_server_version() + except: + return False + if not server_version == SERVER_VERSION: + self.emit(SIGNAL("error_box"), "server is version %s client accepts version %s" % (server_version, SERVER_VERSION)) + return False + return True + + def _proxyError(self, func, e): + """ + formats proxy error msg + """ + msg = "proxy error in '%s':\n%s" % (func, e) + self.errorQueue.append(msg) + + def getError(self): + self.mutex.lock() + if len(self.errorQueue) > 0: + err = self.errorQueue.pop() + print err + self.emit(SIGNAL("error_box"), err) + self.mutex.unlock() + + def getLinkCollector(self): + """ + grab links from collector and return the ids + """ + self.mutex.lock() + try: + return self.proxy.get_collector_files() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getLinkCollector", e) + finally: + self.mutex.unlock() + + def getPackageCollector(self): + """ + grab packages from collector and return the data + """ + self.mutex.lock() + try: + return self.proxy.get_collector_packages() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getPackageCollector", e) + finally: + self.mutex.unlock() + + def getLinkInfo(self, id): + """ + grab file info for the given id and return it + """ + self.mutex.lock() + try: + return self.proxy.get_file_info(id) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getLinkInfo", e) + finally: + self.mutex.unlock() + + def getPackageInfo(self, id): + """ + grab package info for the given id and return it + """ + self.mutex.lock() + try: + return self.proxy.get_package_data(id) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getPackageInfo", e) + finally: + self.mutex.unlock() + + def getPackageQueue(self): + """ + grab queue return the data + """ + self.mutex.lock() + try: + return self.proxy.get_queue() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getPackageQueue", e) + finally: + self.mutex.unlock() + + def getPackageFiles(self, id): + """ + grab package files and return ids + """ + self.mutex.lock() + try: + return self.proxy.get_package_files(id) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getPackageFiles", e) + finally: + self.mutex.unlock() + + def getDownloadQueue(self): + """ + grab files that are currently downloading and return info + """ + self.mutex.lock() + try: + return self.proxy.status_downloads() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getDownloadQueue", e) + finally: + self.mutex.unlock() + + def getServerStatus(self): + """ + return server status + """ + self.mutex.lock() + try: + return self.proxy.status_server() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getServerStatus", e) + finally: + self.mutex.unlock() + + def addURLs(self, links): + """ + add links to collector + """ + self.mutex.lock() + try: + self.proxy.add_urls(links) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "addURLs", e) + finally: + self.mutex.unlock() + + def togglePause(self): + """ + toogle pause + """ + self.mutex.lock() + try: + return self.proxy.toggle_pause() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "togglePause", e) + finally: + self.mutex.unlock() + + def setPause(self, pause): + """ + set pause + """ + self.mutex.lock() + try: + if pause: + self.proxy.pause_server() + else: + self.proxy.unpause_server() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "setPause", e) + finally: + self.mutex.unlock() + + def newPackage(self, name): + """ + create a new package and return id + """ + self.mutex.lock() + try: + return self.proxy.new_package(name) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "newPackage", e) + finally: + self.mutex.unlock() + + def addFileToPackage(self, fileid, packid): + """ + add a file from collector to package + """ + self.mutex.lock() + try: + self.proxy.move_file_2_package(fileid, packid) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "addFileToPackage", e) + finally: + self.mutex.unlock() + + def pushPackageToQueue(self, packid): + """ + push a package to queue + """ + self.mutex.lock() + try: + self.proxy.push_package_2_queue(packid) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "pushPackageToQueue", e) + finally: + self.mutex.unlock() + + def restartPackage(self, packid): + """ + restart a package + """ + self.mutex.lock() + try: + self.proxy.restart_package(packid) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "restartPackage", e) + finally: + self.mutex.unlock() + + def restartFile(self, fileid): + """ + restart a file + """ + self.mutex.lock() + try: + self.proxy.restart_file(fileid) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "restartFile", e) + finally: + self.mutex.unlock() + + def removePackage(self, packid): + """ + remove a package + """ + self.mutex.lock() + try: + self.proxy.del_packages([packid,]) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "removePackage", e) + finally: + self.mutex.unlock() + + def removeFile(self, fileid): + """ + remove a file + """ + self.mutex.lock() + try: + self.proxy.del_links([fileid,]) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "removeFile", e) + finally: + self.mutex.unlock() + + def uploadContainer(self, filename, type, content): + """ + upload a container + """ + self.mutex.lock() + try: + self.proxy.upload_container(filename, type, content) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "uploadContainer", e) + finally: + self.mutex.unlock() + + def getLog(self, offset): + """ + get log + """ + self.mutex.lock() + try: + return self.proxy.get_log(offset) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getLog", e) + finally: + self.mutex.unlock() + + def stopAllDownloads(self): + """ + get log + """ + self.mutex.lock() + try: + self.proxy.pause_server() + self.proxy.stop_downloads() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "stopAllDownloads", e) + finally: + self.mutex.unlock() + + def updateAvailable(self): + """ + update available + """ + self.mutex.lock() + try: + return self.proxy.update_available() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "updateAvailable", e) + finally: + self.mutex.unlock() + + def setPackageName(self, pid, name): + """ + set new package name + """ + self.mutex.lock() + try: + return self.proxy.set_package_name(pid, name) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "setPackageName", e) + finally: + self.mutex.unlock() + + def pullOutPackage(self, pid): + """ + pull out package + """ + self.mutex.lock() + try: + return self.proxy.pull_out_package(pid) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "pullOutPackage", e) + finally: + self.mutex.unlock() + + def captchaWaiting(self): + """ + is the a captcha waiting? + """ + self.mutex.lock() + try: + return self.proxy.is_captcha_waiting() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "captchaWaiting", e) + finally: + self.mutex.unlock() + + def getCaptcha(self): + """ + get captcha + """ + self.mutex.lock() + try: + return self.proxy.get_captcha_task() + except Exception, e: + self.emit(SIGNAL("proxy_error"), "getCaptcha", e) + finally: + self.mutex.unlock() + + def setCaptchaResult(self, cid, result): + """ + get captcha + """ + self.mutex.lock() + try: + return self.proxy.set_captcha_result(cid, result) + except Exception, e: + self.emit(SIGNAL("proxy_error"), "setCaptchaResult", e) + finally: + self.mutex.unlock() + |