# -*- 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 . @author: mkaay """ from PyQt4.QtCore import * from PyQt4.QtGui import * from module.remote.thriftbackend.thriftgen.pyload.ttypes 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()) translatedStatusMap = {} # -> CollectorModel.__init__ def formatSize(size): """formats size of bytes""" size = int(size) steps = 0 sizes = ["B", "KiB", "MiB", "GiB", "TiB"] while size > 1000: size /= 1024.0 steps += 1 return "%.2f %s" % (size, sizes[steps]) class CollectorModel(QAbstractItemModel): """ model for the collector view """ def __init__(self, view, connector): QAbstractItemModel.__init__(self) self.connector = connector self.view = view self._data = [] self.cols = 4 self.interval = 1 self.mutex = QMutex() global translatedStatusMap # workaround because i18n is not running at import time translatedStatusMap = { "finished": _("finished"), "offline": _("offline"), "online": _("online"), "queued": _("queued"), "checking": _("checking"), "waiting": _("waiting"), "reconnected": _("reconnected"), "starting": _("starting"), "failed": _("failed"), "aborted": _("aborted"), "decrypting": _("decrypting"), "custom": _("custom"), "downloading": _("downloading"), "processing": _("processing") } def translateStatus(self, string): """ used to convert to locale specific status """ return translatedStatusMap[string] def addEvent(self, event): """ called from main loop, pass events to the correct methods """ locker = QMutexLocker(self.mutex) if event.event == "reload": self.fullReload() elif event.event == "remove": self.removeEvent(event) elif event.event == "insert": self.insertEvent(event) elif event.event == "update": self.updateEvent(event) def fullReload(self): """ reload whole model, used at startup to load initial data """ self._data = [] order = self.connector.getPackageOrder(Destination.Collector) self.beginInsertRows(QModelIndex(), 0, len(order.values())) for position, pid in order.iteritems(): pack = self.connector.getPackageData(pid) package = Package(pack) self._data.append(package) self._data = sorted(self._data, key=lambda p: p.data["order"]) self.endInsertRows() def removeEvent(self, event): """ remove an element from model """ if event.type == ElementType.File: for p, package in enumerate(self._data): for k, child in enumerate(package.children): if child.id == event.id: 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 == event.id: self.beginRemoveRows(QModelIndex(), k, k) del self._data[k] self.endRemoveRows() break def insertEvent(self, event): """ inserts a new element in the model """ if event.type == ElementType.File: info = self.connector.getFileData(event.id) for k, package in enumerate(self._data): if package.id == info.package: if package.getChild(info.fid): self.updateEvent(event) break self.beginInsertRows(self.index(k, 0), info.order, info.order) package.addChild(info) self.endInsertRows() break else: data = self.connector.getPackageData(event.id) package = Package(data) self.beginInsertRows(QModelIndex(), data.order, data.order) self._data.insert(data.order, package) self.endInsertRows() def updateEvent(self, event): """ update an element in the model """ if event.type == ElementType.File: info = self.connector.proxy.getFileData(event.id) if not info: return for p, package in enumerate(self._data): if package.id == info.package: for k, child in enumerate(package.children): if child.id == event.id: child.update(info) if not info.status == 12: 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.getPackageData(event.id) if not data: return for p, package in enumerate(self._data): if package.id == event.id: package.update(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): """ return cell data """ 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 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(self.translateStatus(statusMapReverse[status])) elif index.column() == 3: item = index.internalPointer() if isinstance(item, Link): return QVariant(formatSize(item.data["size"])) else: ms = 0 for c in item.children: ms += c.data["size"] return QVariant(formatSize(ms)) elif role == Qt.EditRole: if index.column() == 0: return QVariant(index.internalPointer().data["name"]) return QVariant() def index(self, row, column, parent=QModelIndex()): """ creates a cell index with pointer to the data """ if parent == QModelIndex() and len(self._data) > row: pointer = self._data[row] index = self.createIndex(row, column, pointer) elif parent.isValid(): try: pointer = parent.internalPointer().children[row] except: return QModelIndex() index = self.createIndex(row, column, pointer) else: index = QModelIndex() return index def parent(self, index): """ return index of parent element only valid for links """ 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()): """ returns row count for the element """ 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): """ returns column heading """ if orientation == Qt.Horizontal and role == Qt.DisplayRole: if section == 0: return QVariant(_("Name")) elif section == 1: return QVariant(_("Plugin")) elif section == 2: return QVariant(_("Status")) elif section == 3: return QVariant(_("Size")) return QVariant() def flags(self, index): """ cell flags """ 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): """ called if package name editing is finished, sets new name """ 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): """ package object in the model """ def __init__(self, pack): self.id = pack.pid self.children = [] for f in pack.links: self.addChild(f) self.data = {} self.update(pack) def update(self, pack): """ update data dict from thift object """ data = { "name": pack.name, "folder": pack.folder, "site": pack.site, "password": pack.password, "order": pack.order, "priority": pack.priority, } self.data.update(data) def addChild(self, f): """ add child (Link) to package """ self.children.insert(f.order, Link(f, self)) self.children = sorted(self.children, key=lambda l: l.data["order"]) def getChild(self, fid): """ get child from package """ for child in self.children: if child.id == int(fid): return child return None def getChildKey(self, fid): """ get child index """ for k, child in enumerate(self.children): if child.id == int(fid): return k return None def removeChild(self, fid): """ remove child """ for k, child in enumerate(self.children): if child.id == int(fid): del self.children[k] class Link(object): def __init__(self, f, pack): self.data = {"downloading": None} self.update(f) self.id = f.fid self.package = pack def update(self, f): """ update data dict from thift object """ data = { "url": f.url, "name": f.name, "plugin": f.plugin, "size": f.size, "forrmat_size": f.format_size, "status": f.status, "statusmsg": f.statusmsg, "package": f.packageID, "error": f.error, "order": f.order, "progress": f.progress } self.data.update(data) class CollectorView(QTreeView): """ view component for collector """ 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.setColumnWidth(3, 100) self.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)