summaryrefslogtreecommitdiffstats
path: root/module/gui
diff options
context:
space:
mode:
authorGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2010-08-25 18:22:27 +0200
committerGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2010-08-25 18:22:27 +0200
commit29f9dc8fb3396b03d732ebcbeb1cc8f00fe13897 (patch)
treef2a910cbea747a7b0c0a50d6c66691e54f5ef47f /module/gui
parentmerged gui (diff)
downloadpyload-29f9dc8fb3396b03d732ebcbeb1cc8f00fe13897.tar.xz
new dirs
Diffstat (limited to 'module/gui')
-rw-r--r--module/gui/Accounts.py167
-rw-r--r--module/gui/CaptchaDock.py85
-rw-r--r--module/gui/Collector.py289
-rw-r--r--module/gui/ConnectionManager.py261
-rw-r--r--module/gui/CoreConfigParser.py165
-rw-r--r--module/gui/LinkDock.py54
-rw-r--r--module/gui/MainWindow.py512
-rw-r--r--module/gui/PackageDock.py67
-rw-r--r--module/gui/Queue.py252
-rw-r--r--module/gui/SettingsWidget.py108
-rw-r--r--module/gui/XMLParser.py71
-rw-r--r--module/gui/__init__.py1
-rw-r--r--module/gui/connector.py311
13 files changed, 2343 insertions, 0 deletions
diff --git a/module/gui/Accounts.py b/module/gui/Accounts.py
new file mode 100644
index 000000000..f47928c1a
--- /dev/null
+++ b/module/gui/Accounts.py
@@ -0,0 +1,167 @@
+# -*- 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 strftime, gmtime
+
+class AccountModel(QAbstractItemModel):
+ def __init__(self, view, connector):
+ QAbstractItemModel.__init__(self)
+ self.connector = connector
+ self.view = view
+ self._data = []
+ self.cols = 4
+ self.mutex = QMutex()
+
+ def reloadData(self):
+ data = self.connector.proxy.get_accounts()
+ self.beginRemoveRows(QModelIndex(), 0, len(self._data))
+ self._data = []
+ self.endRemoveRows()
+ accounts = []
+ for li in data.values():
+ accounts += li
+ self.beginInsertRows(QModelIndex(), 0, len(accounts))
+ self._data = accounts
+ self.endInsertRows()
+
+ def toData(self, index):
+ return index.internalPointer()
+
+ def data(self, index, role=Qt.DisplayRole):
+ if not index.isValid():
+ return QVariant()
+ if role == Qt.DisplayRole:
+ if index.column() == 0:
+ return QVariant(self.toData(index)["type"])
+ elif index.column() == 1:
+ return QVariant(self.toData(index)["login"])
+ elif index.column() == 2:
+ if not self.toData(index)["validuntil"]:
+ return QVariant(_("n/a"))
+ until = int(self.toData(index)["validuntil"])
+ if until > 0:
+ fmtime = strftime(_("%a, %d %b %Y %H:%M"), gmtime(until))
+ return QVariant(fmtime)
+ else:
+ return QVariant(_("unlimited"))
+ elif index.column() == 3:
+ return QVariant(self.toData(index)["trafficleft"])
+ #elif role == Qt.EditRole:
+ # if index.column() == 0:
+ # return QVariant(index.internalPointer().data["name"])
+ return QVariant()
+
+ def index(self, row, column, parent=QModelIndex()):
+ if parent == QModelIndex() and len(self._data) > row:
+ pointer = self._data[row]
+ index = self.createIndex(row, column, pointer)
+ elif parent.isValid():
+ pointer = parent.internalPointer().children[row]
+ index = self.createIndex(row, column, pointer)
+ else:
+ index = QModelIndex()
+ return index
+
+ def parent(self, index):
+ return QModelIndex()
+
+ def rowCount(self, parent=QModelIndex()):
+ if parent == QModelIndex():
+ return len(self._data)
+ return 0
+
+ def columnCount(self, parent=QModelIndex()):
+ return self.cols
+
+ def hasChildren(self, parent=QModelIndex()):
+ return False
+
+ def canFetchMore(self, parent):
+ return False
+
+ def headerData(self, section, orientation, role=Qt.DisplayRole):
+ if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+ if section == 0:
+ return QVariant(_("Type"))
+ elif section == 1:
+ return QVariant(_("Login"))
+ elif section == 2:
+ return QVariant(_("Valid until"))
+ elif section == 3:
+ return QVariant(_("Traffic left"))
+ return QVariant()
+
+ def flags(self, index):
+ return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
+
+ #def setData(self, index, value, role=Qt.EditRole):
+ # if index.column() == 0 and self.parent(index) == QModelIndex() and role == Qt.EditRole:
+ # self.connector.setPackageName(index.internalPointer().id, str(value.toString()))
+ # return True
+
+class AccountView(QTreeView):
+ def __init__(self, connector):
+ QTreeView.__init__(self)
+ self.setModel(AccountModel(self, connector))
+
+ self.setColumnWidth(0, 150)
+ self.setColumnWidth(1, 150)
+ self.setColumnWidth(2, 150)
+ self.setColumnWidth(3, 150)
+
+ self.setEditTriggers(QAbstractItemView.NoEditTriggers)
+
+ self.delegate = AccountDelegate(self, self.model())
+ self.setItemDelegateForColumn(3, self.delegate)
+
+class AccountDelegate(QItemDelegate):
+ def __init__(self, parent, model):
+ QItemDelegate.__init__(self, parent)
+ self.model = model
+
+ def paint(self, painter, option, index):
+ if not index.isValid():
+ return
+ if index.column() == 3:
+ data = self.model.toData(index)
+ opts = QStyleOptionProgressBarV2()
+ opts.minimum = 0
+ if data["trafficleft"]:
+ if data["trafficleft"] == -1:
+ opts.maximum = opts.progress = 1
+ else:
+ opts.maximum = opts.progress = data["trafficleft"]
+ if data["maxtraffic"]:
+ opts.maximum = data["maxtraffic"]
+
+ 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 data["trafficleft"] and data["trafficleft"] == -1:
+ opts.text = QString(_("unlimited"))
+ else:
+ opts.text = QString.number(round(float(opts.progress)/1024/1024, 2)) + " GB"
+ QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)
+ return
+ QItemDelegate.paint(self, painter, option, index)
+
diff --git a/module/gui/CaptchaDock.py b/module/gui/CaptchaDock.py
new file mode 100644
index 000000000..4f3c9efd0
--- /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..f7bfcbebf
--- /dev/null
+++ b/module/gui/Collector.py
@@ -0,0 +1,289 @@
+# -*- 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 *
+
+statusMap = {
+ "finished": 0,
+ "offline": 1,
+ "online": 2,
+ "queued": 3,
+ "checking": 4,
+ "waiting": 5,
+ "reconnected": 6,
+ "starting": 7,
+ "failed": 8,
+ "aborted": 9,
+ "decrypting": 10,
+ "custom": 11,
+ "downloading": 12,
+ "processing": 13
+}
+statusMapReverse = dict((v,k) for k, v in statusMap.iteritems())
+
+class CollectorModel(QAbstractItemModel):
+ def __init__(self, view, connector):
+ QAbstractItemModel.__init__(self)
+ self.connector = connector
+ self.view = view
+ self._data = []
+ self.cols = 3
+ self.interval = 1
+ self.mutex = QMutex()
+
+ def addEvent(self, event):
+ locker = QMutexLocker(self.mutex)
+ if event[0] == "reload":
+ self.fullReload()
+ elif event[0] == "remove":
+ self.removeEvent(event)
+ elif event[0] == "insert":
+ self.insertEvent(event)
+ elif event[0] == "update":
+ self.updateEvent(event)
+
+ def fullReload(self):
+ self._data = []
+ packs = self.connector.getPackageCollector()
+ self.beginInsertRows(QModelIndex(), 0, len(packs))
+ for pid, data in packs.items():
+ package = Package(pid, data)
+ self._data.append(package)
+ self._data = sorted(self._data, key=lambda p: p.data["order"])
+ self.endInsertRows()
+
+ def removeEvent(self, event):
+ if event[2] == "file":
+ for p, package in enumerate(self._data):
+ for k, child in enumerate(package.children):
+ if child.id == int(event[3]):
+ self.beginRemoveRows(self.index(p, 0), k, k)
+ del package.children[k]
+ self.endRemoveRows()
+ break
+ else:
+ for k, package in enumerate(self._data):
+ if package.id == int(event[3]):
+ self.beginRemoveRows(QModelIndex(), k, k)
+ del self._data[k]
+ self.endRemoveRows()
+ break
+
+ def insertEvent(self, event):
+ if event[2] == "file":
+ info = self.connector.proxy.get_file_data(int(event[3]))
+ fid = info.keys()[0]
+ info = info.values()[0]
+
+ for k, package in enumerate(self._data):
+ if package.id == int(info["package"]):
+ if package.getChild(fid):
+ del event[4]
+ self.updateEvent(event)
+ break
+ self.beginInsertRows(self.index(k, 0), info["order"], info["order"])
+ package.addChild(fid, info, info["order"])
+ self.endInsertRows()
+ break
+ else:
+ data = self.connector.proxy.get_package_data(event[3])
+ package = Package(event[3], data)
+ self.beginInsertRows(QModelIndex(), data["order"], data["order"])
+ self._data.insert(data["order"], package)
+ self.endInsertRows()
+
+ def updateEvent(self, event):
+ if event[2] == "file":
+ info = self.connector.proxy.get_file_data(int(event[3]))
+ if not info:
+ return
+ fid = info.keys()[0]
+ info = info.values()[0]
+ for p, package in enumerate(self._data):
+ if package.id == int(info["package"]):
+ for k, child in enumerate(package.children):
+ if child.id == int(event[3]):
+ child.data = info
+ child.data["downloading"] = None
+ self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(k, 0, self.index(p, 0)), self.index(k, self.cols, self.index(p, self.cols)))
+ break
+ else:
+ data = self.connector.proxy.get_package_data(int(event[3]))
+ if not data:
+ return
+ pid = event[3]
+ del data["links"]
+ for p, package in enumerate(self._data):
+ if package.id == int(pid):
+ package.data = data
+ self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(p, 0), self.index(p, self.cols))
+ break
+
+ def data(self, index, role=Qt.DisplayRole):
+ if not index.isValid():
+ return QVariant()
+ if role == Qt.DisplayRole:
+ if index.column() == 0:
+ return QVariant(index.internalPointer().data["name"])
+ elif index.column() == 2:
+ item = index.internalPointer()
+ status = 0
+ if isinstance(item, Package):
+ for child in item.children:
+ if child.data["status"] > status:
+ status = child.data["status"]
+ else:
+ status = item.data["status"]
+ return QVariant(statusMapReverse[status])
+ elif index.column() == 1:
+ item = index.internalPointer()
+ plugins = []
+ if isinstance(item, Package):
+ for child in item.children:
+ if not child.data["plugin"] in plugins:
+ plugins.append(child.data["plugin"])
+ else:
+ plugins.append(item.data["plugin"])
+ return QVariant(", ".join(plugins))
+ elif role == Qt.EditRole:
+ if index.column() == 0:
+ return QVariant(index.internalPointer().data["name"])
+ return QVariant()
+
+ def index(self, row, column, parent=QModelIndex()):
+ if parent == QModelIndex() and len(self._data) > row:
+ pointer = self._data[row]
+ index = self.createIndex(row, column, pointer)
+ elif parent.isValid():
+ pointer = parent.internalPointer().children[row]
+ index = self.createIndex(row, column, pointer)
+ else:
+ index = QModelIndex()
+ return index
+
+ def parent(self, index):
+ if index == QModelIndex():
+ return QModelIndex()
+ if index.isValid():
+ link = index.internalPointer()
+ if isinstance(link, Link):
+ for k, pack in enumerate(self._data):
+ if pack == link.package:
+ return self.createIndex(k, 0, link.package)
+ return QModelIndex()
+
+ def rowCount(self, parent=QModelIndex()):
+ if parent == QModelIndex():
+ #return package count
+ return len(self._data)
+ else:
+ if parent.isValid():
+ #index is valid
+ pack = parent.internalPointer()
+ if isinstance(pack, Package):
+ #index points to a package
+ #return len of children
+ return len(pack.children)
+ else:
+ #index is invalid
+ return False
+ #files have no children
+ return 0
+
+ def columnCount(self, parent=QModelIndex()):
+ return self.cols
+
+ def hasChildren(self, parent=QModelIndex()):
+ if not parent.isValid():
+ return True
+ return (self.rowCount(parent) > 0)
+
+ def canFetchMore(self, parent):
+ return False
+
+ def headerData(self, section, orientation, role=Qt.DisplayRole):
+ if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+ if section == 0:
+ return QVariant(_("Name"))
+ elif section == 2:
+ return QVariant(_("Status"))
+ elif section == 1:
+ return QVariant(_("Plugin"))
+ return QVariant()
+
+ def flags(self, index):
+ if index.column() == 0 and self.parent(index) == QModelIndex():
+ return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
+ return Qt.ItemIsSelectable | Qt.ItemIsEnabled
+
+ def setData(self, index, value, role=Qt.EditRole):
+ if index.column() == 0 and self.parent(index) == QModelIndex() and role == Qt.EditRole:
+ self.connector.setPackageName(index.internalPointer().id, str(value.toString()))
+ return True
+
+class Package(object):
+ def __init__(self, pid, data):
+ self.id = int(pid)
+ self.children = []
+ for fid, fdata in data["links"].items():
+ self.addChild(int(fid), fdata)
+ del data["links"]
+ self.data = data
+
+ def addChild(self, fid, data, pos=None):
+ if pos is None:
+ self.children.insert(data["order"], Link(fid, data, self))
+ else:
+ self.children.insert(pos, Link(fid, data, self))
+ self.children = sorted(self.children, key=lambda l: l.data["order"])
+
+ def getChild(self, fid):
+ for child in self.children:
+ if child.id == int(fid):
+ return child
+ return None
+
+ def getChildKey(self, fid):
+ for k, child in enumerate(self.children):
+ if child.id == int(fid):
+ return k
+ return None
+
+ def removeChild(self, fid):
+ for k, child in enumerate(self.children):
+ if child.id == int(fid):
+ del self.children[k]
+
+class Link(object):
+ def __init__(self, fid, data, pack):
+ self.data = data
+ self.data["downloading"] = None
+ self.id = int(fid)
+ self.package = pack
+
+class CollectorView(QTreeView):
+ def __init__(self, connector):
+ QTreeView.__init__(self)
+ self.setModel(CollectorModel(self, connector))
+ self.setColumnWidth(0, 500)
+ self.setColumnWidth(1, 100)
+ self.setColumnWidth(2, 200)
+
+ self.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
+
diff --git a/module/gui/ConnectionManager.py b/module/gui/ConnectionManager.py
new file mode 100644
index 000000000..0bdeae282
--- /dev/null
+++ b/module/gui/ConnectionManager.py
@@ -0,0 +1,261 @@
+# -*- 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"), 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", "password":""}
+ 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:"))
+ pwLabel = QLabel(_("Password:"))
+ portLabel = QLabel(_("Port:"))
+
+ name = QLineEdit()
+ host = QLineEdit()
+ ssl = QCheckBox()
+ local = QCheckBox()
+ user = QLineEdit()
+ password = QLineEdit()
+ password.setEchoMode(QLineEdit.Password)
+ 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(portLabel, 3, 0)
+ grid.addWidget(port, 3, 1)
+ grid.addWidget(sslLabel, 4, 0)
+ grid.addWidget(ssl, 4, 1)
+ grid.addWidget(userLabel, 5, 0)
+ grid.addWidget(user, 5, 1)
+ grid.addWidget(pwLabel, 6, 0)
+ grid.addWidget(password, 6, 1)
+ grid.addWidget(cancel, 7, 0)
+ grid.addWidget(save, 7, 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["password"] = password
+ 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["password"].setText(data["password"])
+ 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["password"].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["password"].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["password"].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["password"].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["password"] = self.controls["password"].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/CoreConfigParser.py b/module/gui/CoreConfigParser.py
new file mode 100644
index 000000000..0d1d298c6
--- /dev/null
+++ b/module/gui/CoreConfigParser.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+from os.path import exists
+from os.path import join
+
+
+CONF_VERSION = 1
+
+########################################################################
+class ConfigParser:
+
+ #----------------------------------------------------------------------
+ def __init__(self, configdir):
+ """Constructor"""
+ self.configdir = configdir
+ self.config = {}
+
+ if self.checkVersion():
+ self.readConfig()
+
+ #----------------------------------------------------------------------
+ def checkVersion(self):
+
+ if not exists(join(self.configdir, "pyload.conf")):
+ return False
+ f = open(join(self.configdir, "pyload.conf"), "rb")
+ v = f.readline()
+ f.close()
+ v = v[v.find(":")+1:].strip()
+
+ if int(v) < CONF_VERSION:
+ return False
+
+ return True
+
+ #----------------------------------------------------------------------
+ def readConfig(self):
+ """reads the config file"""
+
+ self.config = self.parseConfig(join(self.configdir, "pyload.conf"))
+
+
+ #----------------------------------------------------------------------
+ def parseConfig(self, config):
+ """parses a given configfile"""
+
+ f = open(config)
+
+ config = f.read()
+
+ config = config.split("\n")[1:]
+
+ conf = {}
+
+ section, option, value, typ, desc = "","","","",""
+
+ listmode = False
+
+ for line in config:
+
+ line = line.rpartition("#") # removes comments
+
+ if line[1]:
+ line = line[0]
+ else:
+ line = line[2]
+
+ line = line.strip()
+
+ try:
+
+ if line == "":
+ continue
+ elif line.endswith(":"):
+ section, none, desc = line[:-1].partition('-')
+ section = section.strip()
+ desc = desc.replace('"', "").strip()
+ conf[section] = { "desc" : desc }
+ else:
+ if listmode:
+
+ if line.endswith("]"):
+ listmode = False
+ line = line.replace("]","")
+
+ value += [self.cast(typ, x.strip()) for x in line.split(",") if x]
+
+ if not listmode:
+ conf[section][option] = { "desc" : desc,
+ "type" : typ,
+ "value" : value}
+
+
+ else:
+ content, none, value = line.partition("=")
+
+ content, none, desc = content.partition(":")
+
+ desc = desc.replace('"', "").strip()
+
+ typ, option = content.split()
+
+ value = value.strip()
+
+ if value.startswith("["):
+ if value.endswith("]"):
+ listmode = False
+ value = value[:-1]
+ else:
+ listmode = True
+
+ value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x]
+ else:
+ value = self.cast(typ, value)
+
+ if not listmode:
+ conf[section][option] = { "desc" : desc,
+ "type" : typ,
+ "value" : value}
+
+ except:
+ pass
+
+
+ f.close()
+ return conf
+
+ #----------------------------------------------------------------------
+ def cast(self, typ, value):
+ """cast value to given format"""
+ if type(value) not in (str, unicode):
+ return value
+
+ if typ == "int":
+ return int(value)
+ elif typ == "bool":
+ return True if value.lower() in ("1","true", "on", "an","yes") else False
+ else:
+ return value
+
+ #----------------------------------------------------------------------
+ def get(self, section, option):
+ """get value"""
+ return self.config[section][option]["value"]
+
+ #----------------------------------------------------------------------
+ def __getitem__(self, section):
+ """provides dictonary like access: c['section']['option']"""
+ return Section(self, section)
+
+########################################################################
+class Section:
+ """provides dictionary like access for configparser"""
+
+ #----------------------------------------------------------------------
+ def __init__(self, parser, section):
+ """Constructor"""
+ self.parser = parser
+ self.section = section
+
+ #----------------------------------------------------------------------
+ def __getitem__(self, item):
+ """getitem"""
+ return self.parser.get(self.section, item)
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..4ab840fed
--- /dev/null
+++ b/module/gui/MainWindow.py
@@ -0,0 +1,512 @@
+# -*- 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
+from module.gui.SettingsWidget import SettingsWidget
+
+from module.gui.Collector import CollectorView, Package, Link
+from module.gui.Queue import QueueView
+from module.gui.Accounts import AccountView
+
+class MainWindow(QMainWindow):
+ def __init__(self, connector):
+ """
+ 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 = 3
+
+ #init docks
+ self.newPackDock = NewPackageDock()
+ self.addDockWidget(Qt.RightDockWidgetArea, self.newPackDock)
+ 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["accounts"] = {"w":QWidget()}
+ self.tabs["settings"] = {}
+ self.tabs["settings"]["s"] = QScrollArea()
+ self.tabs["settings"]["w"] = SettingsWidget()
+ self.tabs["settings"]["s"].setWidgetResizable(True)
+ self.tabs["settings"]["s"].setWidget(self.tabs["settings"]["w"])
+ 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["accounts"]["w"], _("Accounts"))
+ self.tabw.addTab(self.tabs["settings"]["s"], _("Settings"))
+ self.tabw.addTab(self.tabs["log"]["w"], _("Log"))
+
+ #init tabs
+ self.init_tabs(connector)
+
+ self.setPriority = Priorty(self)
+
+ #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.tabw, SIGNAL("currentChanged(int)"), self.slotTabChanged)
+
+ 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/toolbar_start.png", QSize(), QIcon.Normal, QIcon.Off)
+ pricon.addFile("icons/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/toolbar_stop.png"), _("Stop"))
+ self.toolbar.addSeparator()
+ self.actions["add"] = self.toolbar.addAction(QIcon("icons/toolbar_add.png"), _("Add"))
+ self.toolbar.addSeparator()
+ self.actions["clipboard"] = self.toolbar.addAction(QIcon("icons/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"))
+ containerAction = self.addMenu.addAction(_("Container"))
+ self.connect(self.actions["add"], SIGNAL("triggered()"), self.slotAdd)
+ self.connect(packageAction, SIGNAL("triggered()"), self.slotShowAddPackage)
+ self.connect(containerAction, SIGNAL("triggered()"), self.slotShowAddContainer)
+
+ def init_tabs(self, connector):
+ """
+ create tabs
+ """
+ #queue
+ self.tabs["queue"]["l"] = QGridLayout()
+ self.tabs["queue"]["w"].setLayout(self.tabs["queue"]["l"])
+ self.tabs["queue"]["view"] = QueueView(connector)
+ self.tabs["queue"]["l"].addWidget(self.tabs["queue"]["view"])
+
+ #collector
+ 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"] = CollectorView(connector)
+ self.tabs["collector"]["l"].addWidget(self.tabs["collector"]["package_view"], 0, 0)
+ self.tabs["collector"]["l"].addWidget(toQueue, 1, 0)
+ self.connect(toQueue, SIGNAL("clicked()"), self.slotPushPackageToQueue)
+ self.tabs["collector"]["package_view"].setContextMenuPolicy(Qt.CustomContextMenu)
+ self.tabs["queue"]["view"].setContextMenuPolicy(Qt.CustomContextMenu)
+
+ #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"])
+
+ #accounts
+ self.tabs["accounts"]["view"] = AccountView(connector)
+ self.tabs["accounts"]["w"].setLayout(QHBoxLayout())
+ self.tabs["accounts"]["w"].layout().addWidget(self.tabs["accounts"]["view"])
+
+ 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/remove_small.png"), _("Remove"), self.queueContext)
+ self.queueContext.buttons["restart"] = QAction(QIcon("icons/refresh_small.png"), _("Restart"), self.queueContext)
+ self.queueContext.buttons["pull"] = QAction(QIcon("icons/pull_small.png"), _("Pull out"), self.queueContext)
+ self.queueContext.buttons["abort"] = QAction(QIcon("icons/abort.png"), _("Abort"), self.queueContext)
+ self.queueContext.buttons["edit"] = QAction(QIcon("icons/edit_small.png"), _("Edit Name"), self.queueContext)
+ self.queuePriorityMenu = QMenu(_("Priority"))
+ self.queuePriorityMenu.actions = {}
+ self.queuePriorityMenu.actions["veryhigh"] = QAction(_("very high"), self.queuePriorityMenu)
+ self.queuePriorityMenu.addAction(self.queuePriorityMenu.actions["veryhigh"])
+ self.queuePriorityMenu.actions["high"] = QAction(_("high"), self.queuePriorityMenu)
+ self.queuePriorityMenu.addAction(self.queuePriorityMenu.actions["high"])
+ self.queuePriorityMenu.actions["normal"] = QAction(_("normal"), self.queuePriorityMenu)
+ self.queuePriorityMenu.addAction(self.queuePriorityMenu.actions["normal"])
+ self.queuePriorityMenu.actions["low"] = QAction(_("low"), self.queuePriorityMenu)
+ self.queuePriorityMenu.addAction(self.queuePriorityMenu.actions["low"])
+ self.queuePriorityMenu.actions["verylow"] = QAction(_("very low"), self.queuePriorityMenu)
+ self.queuePriorityMenu.addAction(self.queuePriorityMenu.actions["verylow"])
+ self.queueContext.addAction(self.queueContext.buttons["pull"])
+ self.queueContext.addAction(self.queueContext.buttons["edit"])
+ self.queueContext.addAction(self.queueContext.buttons["remove"])
+ self.queueContext.addAction(self.queueContext.buttons["restart"])
+ self.queueContext.addAction(self.queueContext.buttons["abort"])
+ self.queueContext.addMenu(self.queuePriorityMenu)
+ 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)
+ self.connect(self.queueContext.buttons["abort"], SIGNAL("triggered()"), self.slotAbortDownload)
+ self.connect(self.queueContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditPackage)
+
+ self.connect(self.queuePriorityMenu.actions["veryhigh"], SIGNAL("triggered()"), self.setPriority.veryHigh)
+ self.connect(self.queuePriorityMenu.actions["high"], SIGNAL("triggered()"), self.setPriority.high)
+ self.connect(self.queuePriorityMenu.actions["normal"], SIGNAL("triggered()"), self.setPriority.normal)
+ self.connect(self.queuePriorityMenu.actions["low"], SIGNAL("triggered()"), self.setPriority.low)
+ self.connect(self.queuePriorityMenu.actions["verylow"], SIGNAL("triggered()"), self.setPriority.veryLow)
+
+ #collector
+ self.collectorContext = QMenu()
+ self.collectorContext.buttons = {}
+ self.collectorContext.item = (None, None)
+ self.collectorContext.buttons["remove"] = QAction(QIcon("icons/remove_small.png"), _("Remove"), self.collectorContext)
+ self.collectorContext.buttons["push"] = QAction(QIcon("icons/push_small.png"), _("Push to queue"), self.collectorContext)
+ self.collectorContext.buttons["edit"] = QAction(QIcon("icons/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 slotAddPackage(self, name, links):
+ """
+ new package
+ let main to the stuff
+ """
+ self.emit(SIGNAL("addPackage"), name, links)
+
+ def slotShowAddContainer(self):
+ """
+ action from add-menu
+ show file selector, emit upload
+ """
+ typeStr = ";;".join([
+ _("All Container Types (%s)") % "*.dlc *.ccf *.rsdf *.txt",
+ _("DLC (%s)") % "*.dlc",
+ _("CCF (%s)") % "*.ccf",
+ _("RSDF (%s)") % "*.rsdf",
+ _("Text Files (%s)") % "*.txt"
+ ])
+ fileNames = QFileDialog.getOpenFileNames(self, _("Open container"), "", typeStr)
+ 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
+ """
+ smodel = self.tabs["collector"]["package_view"].selectionModel()
+ for index in smodel.selectedRows(0):
+ item = index.internalPointer()
+ if isinstance(item, Package):
+ self.emit(SIGNAL("pushPackageToQueue"), item.id)
+ else:
+ self.emit(SIGNAL("pushPackageToQueue"), item.package.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()
+ self.emit(SIGNAL("quit"))
+
+ 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"].indexAt(pos)
+ if not i:
+ return
+ item = i.internalPointer()
+ menuPos = QCursor.pos()
+ menuPos.setX(menuPos.x()+2)
+ self.activeMenu = self.queueContext
+ showAbort = False
+ if isinstance(item, Link) and item.data["downloading"]:
+ showAbort = True
+ elif isinstance(item, Package):
+ for child in item.children:
+ if child.data["downloading"]:
+ showAbort = True
+ if showAbort:
+ self.queueContext.buttons["abort"].setVisible(True)
+ else:
+ self.queueContext.buttons["abort"].setVisible(False)
+ if isinstance(item, Package):
+ self.queueContext.index = i
+ self.queueContext.buttons["edit"].setVisible(True)
+ else:
+ self.queueContext.index = None
+ self.queueContext.buttons["edit"].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"].indexAt(pos)
+ if not i:
+ return
+ item = i.internalPointer()
+ menuPos = QCursor.pos()
+ menuPos.setX(menuPos.x()+2)
+ self.activeMenu = self.collectorContext
+ if isinstance(item, Package):
+ self.collectorContext.index = i
+ self.collectorContext.buttons["edit"].setVisible(True)
+ else:
+ self.collectorContext.index = None
+ 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
+ """
+ smodel = self.tabs["queue"]["view"].selectionModel()
+ for index in smodel.selectedRows(0):
+ item = index.internalPointer()
+ self.emit(SIGNAL("restartDownload"), item.id, isinstance(item, Package))
+ id, isTopLevel = self.queueContext.item
+ if not id == None:
+ self.emit(SIGNAL("restartDownload"), id, isTopLevel)
+
+ def slotRemoveDownload(self):
+ """
+ remove download action is triggered
+ """
+ if self.activeMenu == self.queueContext:
+ view = self.tabs["queue"]["view"]
+ else:
+ view = self.tabs["collector"]["package_view"]
+ smodel = view.selectionModel()
+ for index in smodel.selectedRows(0):
+ item = index.internalPointer()
+ self.emit(SIGNAL("removeDownload"), item.id, isinstance(item, Package))
+
+ def slotToggleClipboard(self, status):
+ """
+ check clipboard (toolbar)
+ """
+ self.emit(SIGNAL("setClipboardStatus"), status)
+
+ def slotEditPackage(self):
+ if self.activeMenu == self.queueContext:
+ view = self.tabs["queue"]["view"]
+ else:
+ view = self.tabs["collector"]["package_view"]
+ view.edit(self.activeMenu.index)
+
+ def slotEditCommit(self, editor):
+ self.emit(SIGNAL("changePackageName"), self.activeMenu.index.internalPointer().id, editor.text())
+
+ def slotPullOutPackage(self):
+ """
+ pull package out of the queue
+ """
+ smodel = self.tabs["queue"]["view"].selectionModel()
+ for index in smodel.selectedRows(0):
+ item = index.internalPointer()
+ if isinstance(item, Package):
+ self.emit(SIGNAL("pullOutPackage"), item.id)
+ else:
+ self.emit(SIGNAL("pullOutPackage"), item.package.id)
+
+ def slotAbortDownload(self):
+ view = self.tabs["queue"]["view"]
+ smodel = view.selectionModel()
+ for index in smodel.selectedRows(0):
+ item = index.internalPointer()
+ self.emit(SIGNAL("abortDownload"), item.id, isinstance(item, Package))
+
+ def changeEvent(self, e):
+ if e.type() == QEvent.WindowStateChange and self.isMinimized():
+ e.ignore()
+ self.hide()
+ self.emit(SIGNAL("hidden"))
+ else:
+ super(MainWindow, self).changeEvent(e)
+
+ def slotTabChanged(self, index):
+ if index == 2:
+ self.emit(SIGNAL("reloadAccounts"))
+ elif index == 3:
+ self.tabs["settings"]["w"].loadConfig()
+
+class Priorty():
+ def __init__(self, win):
+ self.w = win
+
+ def setPriority(self, level):
+ if self.w.activeMenu == self.w.queueContext:
+ smodel = self.w.tabs["queue"]["view"].selectionModel()
+ else:
+ smodel = self.w.tabs["collector"]["package_view"].selectionModel()
+ for index in smodel.selectedRows(0):
+ item = index.internalPointer()
+ pid = item.id if isinstance(item, Package) else item.package.id
+ self.w.emit(SIGNAL("setPriority"), pid, level)
+
+ def veryHigh(self): self.setPriority(2)
+ def high(self): self.setPriority(1)
+ def normal(self): self.setPriority(0)
+ def low(self): self.setPriority(-1)
+ def veryLow(self): self.setPriority(-2)
+
+
+
diff --git a/module/gui/PackageDock.py b/module/gui/PackageDock.py
new file mode 100644
index 000000000..8bd965f16
--- /dev/null
+++ b/module/gui/PackageDock.py
@@ -0,0 +1,67 @@
+# -*- 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):
+ text = str(self.widget.box.toPlainText())
+ lines = []
+ for line in text.splitlines():
+ line = line.strip()
+ if not line:
+ continue
+ lines.append(line)
+ self.emit(SIGNAL("done"), str(self.widget.nameInput.text()), lines)
+ self.widget.nameInput.setText("")
+ self.widget.box.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"))
+
+ self.box = QTextEdit()
+ 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(self.box, 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..8b6f679f8
--- /dev/null
+++ b/module/gui/Queue.py
@@ -0,0 +1,252 @@
+# -*- 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
+
+from module.gui.Collector import CollectorModel, Package, Link, CollectorView, statusMap, statusMapReverse
+
+class QueueModel(CollectorModel):
+ def __init__(self, view, connector):
+ CollectorModel.__init__(self, view, connector)
+ self.cols = 5
+ self.wait_dict = {}
+
+ self.updater = self.QueueUpdater(self.interval)
+ self.connect(self.updater, SIGNAL("update()"), self.update)
+
+ class QueueUpdater(QObject):
+ def __init__(self, interval):
+ QObject.__init__(self)
+
+ self.interval = interval
+ self.timer = QTimer()
+ self.timer.connect(self.timer, SIGNAL("timeout()"), self, SIGNAL("update()"))
+
+ def start(self):
+ self.timer.start(1000)
+
+ def stop(self):
+ self.timer.stop()
+
+ def start(self):
+ self.updater.start()
+
+ def stop(self):
+ self.updater.stop()
+
+ def fullReload(self):
+ self._data = []
+ packs = self.connector.getPackageQueue()
+ self.beginInsertRows(QModelIndex(), 0, len(packs))
+ for pid, data in packs.items():
+ package = Package(pid, data)
+ self._data.append(package)
+ self._data = sorted(self._data, key=lambda p: p.data["order"])
+ self.endInsertRows()
+
+ def update(self):
+ locker = QMutexLocker(self.mutex)
+ downloading = self.connector.getDownloadQueue()
+ for p, pack in enumerate(self._data):
+ for d in downloading:
+ child = pack.getChild(d["id"])
+ if child:
+ child.data["downloading"] = d
+ k = pack.getChildKey(d["id"])
+ self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(k, 0, self.index(p, 0)), self.index(k, self.cols, self.index(p, self.cols)))
+
+ def headerData(self, section, orientation, role=Qt.DisplayRole):
+ if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+ if section == 0:
+ return QVariant(_("Name"))
+ elif section == 2:
+ return QVariant(_("Status"))
+ elif section == 1:
+ return QVariant(_("Plugin"))
+ elif section == 3:
+ return QVariant(_("Priority"))
+ elif section == 4:
+ return QVariant(_("Progress"))
+ return QVariant()
+
+ def getWaitingProgress(self, item):
+ locker = QMutexLocker(self.mutex)
+ if isinstance(item, Link):
+ if item.data["status"] == 5 and item.data["downloading"]:
+ until = float(item.data["downloading"]["wait_until"])
+ try:
+ since, until_old = self.wait_dict[item.id]
+ if not until == until_old:
+ raise Exception
+ except:
+ since = time()
+ self.wait_dict[item.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, item):
+ locker = QMutexLocker(self.mutex)
+ if isinstance(item, Link):
+ if item.data["downloading"]:
+ return int(item.data["downloading"]["percent"])
+ if item.data["statusmsg"] == "finished" or \
+ item.data["statusmsg"] == "failed" or \
+ item.data["statusmsg"] == "aborted":
+ return 100
+ elif isinstance(item, Package):
+ count = len(item.children)
+ perc_sum = 0
+ for child in item.children:
+ val = 0
+ if child.data["downloading"]:
+ val = int(child.data["downloading"]["percent"])
+ elif child.data["statusmsg"] == "finished" or \
+ child.data["statusmsg"] == "failed" or \
+ child.data["statusmsg"] == "aborted":
+ val = 100
+ perc_sum += val
+ if count == 0:
+ return 0
+ return perc_sum/count
+ return 0
+
+ def getSpeed(self, item):
+ if isinstance(item, Link):
+ if item.data["downloading"]:
+ return int(item.data["downloading"]["speed"])
+ elif isinstance(item, Package):
+ count = len(item.children)
+ speed_sum = 0
+ all_waiting = True
+ running = False
+ for child in item.children:
+ val = 0
+ if child.data["downloading"]:
+ if not child.data["statusmsg"] == "waiting":
+ all_waiting = False
+ val = int(child.data["downloading"]["speed"])
+ running = True
+ speed_sum += val
+ if count == 0 or not running or all_waiting:
+ return None
+ return speed_sum
+ return None
+
+ def data(self, index, role=Qt.DisplayRole):
+ if not index.isValid():
+ return QVariant()
+ if role == Qt.DisplayRole:
+ if index.column() == 0:
+ return QVariant(index.internalPointer().data["name"])
+ elif index.column() == 1:
+ item = index.internalPointer()
+ plugins = []
+ if isinstance(item, Package):
+ for child in item.children:
+ if not child.data["plugin"] in plugins:
+ plugins.append(child.data["plugin"])
+ else:
+ plugins.append(item.data["plugin"])
+ return QVariant(", ".join(plugins))
+ elif index.column() == 2:
+ item = index.internalPointer()
+ status = 0
+ speed = self.getSpeed(item)
+ if isinstance(item, Package):
+ for child in item.children:
+ if child.data["status"] > status:
+ status = child.data["status"]
+ else:
+ status = item.data["status"]
+
+ if speed == None or status == 7 or status == 10 or status == 5:
+ return QVariant(statusMapReverse[status])
+ else:
+ return QVariant("%s (%s KB/s)" % (statusMapReverse[status], speed))
+ elif index.column() == 3:
+ item = index.internalPointer()
+ if isinstance(item, Package):
+ return QVariant(item.data["priority"])
+ elif role == Qt.EditRole:
+ if index.column() == 0:
+ return QVariant(index.internalPointer().data["name"])
+ return QVariant()
+
+ def flags(self, index):
+ if index.column() == 0 and self.parent(index) == QModelIndex():
+ return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
+ return Qt.ItemIsSelectable | Qt.ItemIsEnabled
+
+class QueueView(CollectorView):
+ def __init__(self, connector):
+ CollectorView.__init__(self, connector)
+ self.setModel(QueueModel(self, connector))
+
+ self.setColumnWidth(0, 300)
+ self.setColumnWidth(1, 100)
+ self.setColumnWidth(2, 150)
+ self.setColumnWidth(3, 50)
+ self.setColumnWidth(4, 100)
+
+ self.setEditTriggers(QAbstractItemView.NoEditTriggers)
+
+ self.delegate = QueueProgressBarDelegate(self, self.model())
+ self.setItemDelegateForColumn(4, self.delegate)
+
+class QueueProgressBarDelegate(QItemDelegate):
+ def __init__(self, parent, queue):
+ QItemDelegate.__init__(self, parent)
+ self.queue = queue
+
+ def paint(self, painter, option, index):
+ if not index.isValid():
+ return
+ if index.column() == 4:
+ item = index.internalPointer()
+ w = self.queue.getWaitingProgress(item)
+ wait = None
+ if w:
+ progress = w[0]
+ wait = w[1]
+ else:
+ progress = self.queue.getProgress(item)
+ 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)
+
diff --git a/module/gui/SettingsWidget.py b/module/gui/SettingsWidget.py
new file mode 100644
index 000000000..6197cee6c
--- /dev/null
+++ b/module/gui/SettingsWidget.py
@@ -0,0 +1,108 @@
+# -*- 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 sip import delete
+
+class SettingsWidget(QWidget):
+ def __init__(self):
+ QWidget.__init__(self)
+ self.connector = None
+ self.sections = {}
+ self.psections = {}
+ self.data = None
+ self.pdata = None
+ self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
+
+ def setConnector(self, connector):
+ self.connector = connector
+
+ def loadConfig(self):
+ if self.layout():
+ delete(self.layout())
+ for s in self.sections.values()+self.psections.values():
+ delete(s)
+ self.sections = {}
+ self.setLayout(QVBoxLayout())
+ self.clearConfig()
+ layout = self.layout()
+ layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
+
+ self.data = self.connector.proxy.get_config()
+ self.pdata = self.connector.proxy.get_plugin_config()
+ for k, section in self.data.items():
+ s = Section(section, self)
+ self.sections[k] = s
+ layout.addWidget(s)
+ for k, section in self.pdata.items():
+ s = Section(section, self, "plugin")
+ self.psections[k] = s
+ layout.addWidget(s)
+
+ rel = QPushButton(_("Reload"))
+ layout.addWidget(rel)
+ save = QPushButton(_("Save"))
+ #layout.addWidget(save)
+ self.connect(rel, SIGNAL("clicked()"), self.loadConfig)
+
+ def clearConfig(self):
+ self.sections = {}
+
+class Section(QGroupBox):
+ def __init__(self, data, parent, ctype="core"):
+ self.data = data
+ QGroupBox.__init__(self, data["desc"], parent)
+ self.labels = {}
+ self.inputs = {}
+ self.ctype = ctype
+ layout = QGridLayout(self)
+ self.setLayout(layout)
+
+ row = 0
+ for k, option in self.data.items():
+ if k == "desc":
+ continue
+ l = QLabel(option["desc"], self)
+ l.setMinimumWidth(400)
+ self.labels[k] = l
+ layout.addWidget(l, row, 0)
+ if option["type"] == "int":
+ i = QSpinBox(self)
+ i.setMaximum(999999)
+ i.setValue(int(option["value"]))
+ elif not option["type"].find(";") == -1:
+ choices = option["type"].split(";")
+ i = QComboBox(self)
+ i.addItems(choices)
+ i.setCurrentIndex(i.findText(option["value"]))
+ elif option["type"] == "bool":
+ i = QComboBox(self)
+ i.addItem(_("Yes"), QVariant(True))
+ i.addItem(_("No"), QVariant(False))
+ if option["value"]:
+ i.setCurrentIndex(0)
+ else:
+ i.setCurrentIndex(1)
+ else:
+ i = QLineEdit(self)
+ i.setText(option["value"])
+ self.inputs[k] = i
+ #i.setMaximumWidth(300)
+ layout.addWidget(i, row, 1)
+ row += 1
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..975e1ca1b
--- /dev/null
+++ b/module/gui/connector.py
@@ -0,0 +1,311 @@
+# -*- 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.4.1-dev"
+
+from time import sleep
+from uuid import uuid4 as uuid
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+from xmlrpclib import ServerProxy
+import socket
+
+class Connector(QThread):
+ def __init__(self):
+ QThread.__init__(self)
+ self.mutex = QMutex()
+ self.addr = None
+ self.errorQueue = []
+ self.connectionID = None
+ self.running = True
+ self.proxy = self.Dummy()
+
+ def setAddr(self, addr):
+ """
+ set new address
+ """
+ self.mutex.lock()
+ self.addr = addr
+ self.mutex.unlock()
+
+ def connectProxy(self):
+ self.proxy = DispatchRPC(self.mutex, ServerProxy(self.addr, allow_none=True, verbose=False))
+ self.connect(self.proxy, SIGNAL("proxy_error"), self._proxyError)
+ self.connect(self.proxy, SIGNAL("connectionLost"), self, SIGNAL("connectionLost"))
+ try:
+ server_version = self.proxy.get_server_version()
+ self.connectionID = uuid().hex
+ except:
+ return False
+ if not server_version:
+ return False
+ elif 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 canConnect(self):
+ return self.connectProxy()
+
+ 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 stop(self):
+ """
+ stop thread
+ """
+ self.running = False
+
+ def run(self):
+ """
+ start thread
+ (called from thread.start())
+ """
+ self.canConnect()
+ while self.running:
+ sleep(1)
+ self.getError()
+
+ class Dummy(object):
+ def __getattr__(self, attr):
+ def dummy(*args, **kwargs):
+ return None
+ return dummy
+
+ def getPackageCollector(self):
+ """
+ grab packages from collector and return the data
+ """
+ return self.proxy.get_collector()
+
+ def getLinkInfo(self, id):
+ """
+ grab file info for the given id and return it
+ """
+ w = self.proxy.get_file_info
+ w.error = False
+ info = w(id)
+ if not info: return None
+ info["downloading"] = None
+ return info
+
+ def getPackageInfo(self, id):
+ """
+ grab package info for the given id and return it
+ """
+ w = self.proxy.get_package_data
+ w.error = False
+ return w(id)
+
+ def getPackageQueue(self):
+ """
+ grab queue return the data
+ """
+ return self.proxy.get_queue()
+
+ def getPackageFiles(self, id):
+ """
+ grab package files and return ids
+ """
+ return self.proxy.get_package_files(id)
+
+ def getDownloadQueue(self):
+ """
+ grab files that are currently downloading and return info
+ """
+ return self.proxy.status_downloads()
+
+ def getServerStatus(self):
+ """
+ return server status
+ """
+ return self.proxy.status_server()
+
+ def addURLs(self, links):
+ """
+ add links to collector
+ """
+ self.proxy.add_urls(links)
+
+ def togglePause(self):
+ """
+ toogle pause
+ """
+ return self.proxy.toggle_pause()
+
+ def setPause(self, pause):
+ """
+ set pause
+ """
+ if pause:
+ self.proxy.pause_server()
+ else:
+ self.proxy.unpause_server()
+
+ def newPackage(self, name):
+ """
+ create a new package and return id
+ """
+ return self.proxy.new_package(name)
+
+ def addFileToPackage(self, fileid, packid):
+ """
+ add a file from collector to package
+ """
+ self.proxy.move_file_2_package(fileid, packid)
+
+ def pushPackageToQueue(self, packid):
+ """
+ push a package to queue
+ """
+ self.proxy.push_package_2_queue(packid)
+
+ def restartPackage(self, packid):
+ """
+ restart a package
+ """
+ self.proxy.restart_package(packid)
+
+ def restartFile(self, fileid):
+ """
+ restart a file
+ """
+ self.proxy.restart_file(fileid)
+
+ def removePackage(self, packid):
+ """
+ remove a package
+ """
+ self.proxy.del_packages([packid,])
+
+ def removeFile(self, fileid):
+ """
+ remove a file
+ """
+ self.proxy.del_links([fileid,])
+
+ def uploadContainer(self, filename, type, content):
+ """
+ upload a container
+ """
+ self.proxy.upload_container(filename, type, content)
+
+ def getLog(self, offset):
+ """
+ get log
+ """
+ return self.proxy.get_log(offset)
+
+ def stopAllDownloads(self):
+ """
+ get log
+ """
+ self.proxy.pause_server()
+ self.proxy.stop_downloads()
+
+ def updateAvailable(self):
+ """
+ update available
+ """
+ return self.proxy.update_available()
+
+ def setPackageName(self, pid, name):
+ """
+ set new package name
+ """
+ return self.proxy.set_package_name(pid, name)
+
+ def pullOutPackage(self, pid):
+ """
+ pull out package
+ """
+ return self.proxy.pull_out_package(pid)
+
+ def captchaWaiting(self):
+ """
+ is the a captcha waiting?
+ """
+ return self.proxy.is_captcha_waiting()
+
+ def getCaptcha(self):
+ """
+ get captcha
+ """
+ return self.proxy.get_captcha_task()
+
+ def setCaptchaResult(self, cid, result):
+ """
+ get captcha
+ """
+ return self.proxy.set_captcha_result(cid, result)
+
+ def getCaptchaStatus(self, cid):
+ """
+ get captcha status
+ """
+ return self.proxy.get_task_status(cid)
+
+ def getEvents(self):
+ """
+ get events
+ """
+ return self.proxy.get_events(self.connectionID)
+
+class DispatchRPC(QObject):
+ def __init__(self, mutex, server):
+ QObject.__init__(self)
+ self.mutex = mutex
+ self.server = server
+
+ def __getattr__(self, attr):
+ self.mutex.lock()
+ self.fname = attr
+ f = self.Wrapper(getattr(self.server, attr), self.mutex, self)
+ return f
+
+ class Wrapper(object):
+ def __init__(self, f, mutex, dispatcher):
+ self.f = f
+ self.mutex = mutex
+ self.dispatcher = dispatcher
+ self.error = True
+
+ def __call__(self, *args, **kwargs):
+ try:
+ return self.f(*args, **kwargs)
+ except socket.error:
+ self.dispatcher.emit(SIGNAL("connectionLost"))
+ except Exception, e:
+ if self.error:
+ self.dispatcher.emit(SIGNAL("proxy_error"), self.dispatcher.fname, e)
+ finally:
+ self.mutex.unlock()