summaryrefslogtreecommitdiffstats
path: root/module/gui
diff options
context:
space:
mode:
Diffstat (limited to 'module/gui')
-rw-r--r--module/gui/CaptchaDock.py85
-rw-r--r--module/gui/Collector.py312
-rw-r--r--module/gui/ConnectionManager.py249
-rw-r--r--module/gui/LinkDock.py54
-rw-r--r--module/gui/MainWindow.py421
-rw-r--r--module/gui/PWInputWindow.py47
-rw-r--r--module/gui/PackageDock.py76
-rw-r--r--module/gui/Queue.py373
-rw-r--r--module/gui/XMLParser.py71
-rw-r--r--module/gui/__init__.py1
-rw-r--r--module/gui/connector.py424
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()
+