Beispiel #1
0
    def __init__(self, parent=None, signalManager=None,
                 name="Databases update", wantCloseButton=False,
                 searchString="", showAll=True, domains=None,
                 accessCode=""):
        OWWidget.__init__(self, parent, signalManager, name, wantMainArea=False)
        self.searchString = searchString
        self.accessCode = accessCode
        self.showAll = showAll
        self.domains = domains
        self.serverFiles = serverfiles.ServerFiles()

        box = OWGUI.widgetBox(self.controlArea, orientation="horizontal")

        self.lineEditFilter = \
            OWGUIEx.lineEditHint(box, self, "searchString", "Filter",
                                 caseSensitive=False,
                                 delimiters=" ",
                                 matchAnywhere=True,
                                 listUpdateCallback=self.SearchUpdate,
                                 callbackOnType=True,
                                 callback=self.SearchUpdate)

        box = OWGUI.widgetBox(self.controlArea, "Files")
        self.filesView = QTreeWidget(self)
        self.filesView.setHeaderLabels(
            ["", "Data Source", "Update", "Last Updated", "Size"])

        self.filesView.setRootIsDecorated(False)
        self.filesView.setUniformRowHeights(True)
        self.filesView.setSelectionMode(QAbstractItemView.NoSelection)
        self.filesView.setSortingEnabled(True)
        self.filesView.sortItems(1, Qt.AscendingOrder)
        self.filesView.setItemDelegateForColumn(
            0, UpdateOptionsItemDelegate(self.filesView))

        QObject.connect(self.filesView.model(),
                        SIGNAL("layoutChanged()"),
                        self.SearchUpdate)
        box.layout().addWidget(self.filesView)

        box = OWGUI.widgetBox(self.controlArea, orientation="horizontal")
        self.updateButton = OWGUI.button(box, self, "Update all",
                     callback=self.UpdateAll,
                     tooltip="Update all updatable files",
                     )
        
        self.downloadButton = OWGUI.button(box, self, "Download all",
                     callback=self.DownloadFiltered,
                     tooltip="Download all filtered files shown")
        self.cancelButton = OWGUI.button(box, self, "Cancel", callback=self.Cancel,
                     tooltip="Cancel scheduled downloads/updates.")
        OWGUI.rubber(box)
        OWGUI.lineEdit(box, self, "accessCode", "Access Code",
                       orientation="horizontal",
                       callback=self.RetrieveFilesList)
        self.retryButton = OWGUI.button(box, self, "Retry",
                                        callback=self.RetrieveFilesList)
        self.retryButton.hide()
        box = OWGUI.widgetBox(self.controlArea, orientation="horizontal")
        OWGUI.rubber(box)
        if wantCloseButton:
            OWGUI.button(box, self, "Close",
                         callback=self.accept,
                         tooltip="Close")

        self.infoLabel = QLabel()
        self.infoLabel.setAlignment(Qt.AlignCenter)

        self.controlArea.layout().addWidget(self.infoLabel)
        self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.updateItems = []

        self.resize(800, 600)

        self.progress = ProgressState(self, maximum=3)
        self.progress.valueChanged.connect(self._updateProgress)
        self.progress.rangeChanged.connect(self._updateProgress)
        self.executor = ThreadExecutor(
            threadPool=QThreadPool(maxThreadCount=2)
        )

        task = Task(self, function=self.RetrieveFilesList)
        task.exceptionReady.connect(self.HandleError)
        task.start()

        self._tasks = []
        self._haveProgress = False
Beispiel #2
0
class OWDatabasesUpdate(OWWidget):
    def __init__(self, parent=None, signalManager=None,
                 name="Databases update", wantCloseButton=False,
                 searchString="", showAll=True, domains=None,
                 accessCode=""):
        OWWidget.__init__(self, parent, signalManager, name, wantMainArea=False)
        self.searchString = searchString
        self.accessCode = accessCode
        self.showAll = showAll
        self.domains = domains
        self.serverFiles = serverfiles.ServerFiles()

        box = OWGUI.widgetBox(self.controlArea, orientation="horizontal")

        self.lineEditFilter = \
            OWGUIEx.lineEditHint(box, self, "searchString", "Filter",
                                 caseSensitive=False,
                                 delimiters=" ",
                                 matchAnywhere=True,
                                 listUpdateCallback=self.SearchUpdate,
                                 callbackOnType=True,
                                 callback=self.SearchUpdate)

        box = OWGUI.widgetBox(self.controlArea, "Files")
        self.filesView = QTreeWidget(self)
        self.filesView.setHeaderLabels(
            ["", "Data Source", "Update", "Last Updated", "Size"])

        self.filesView.setRootIsDecorated(False)
        self.filesView.setUniformRowHeights(True)
        self.filesView.setSelectionMode(QAbstractItemView.NoSelection)
        self.filesView.setSortingEnabled(True)
        self.filesView.sortItems(1, Qt.AscendingOrder)
        self.filesView.setItemDelegateForColumn(
            0, UpdateOptionsItemDelegate(self.filesView))

        QObject.connect(self.filesView.model(),
                        SIGNAL("layoutChanged()"),
                        self.SearchUpdate)
        box.layout().addWidget(self.filesView)

        box = OWGUI.widgetBox(self.controlArea, orientation="horizontal")
        self.updateButton = OWGUI.button(box, self, "Update all",
                     callback=self.UpdateAll,
                     tooltip="Update all updatable files",
                     )
        
        self.downloadButton = OWGUI.button(box, self, "Download all",
                     callback=self.DownloadFiltered,
                     tooltip="Download all filtered files shown")
        self.cancelButton = OWGUI.button(box, self, "Cancel", callback=self.Cancel,
                     tooltip="Cancel scheduled downloads/updates.")
        OWGUI.rubber(box)
        OWGUI.lineEdit(box, self, "accessCode", "Access Code",
                       orientation="horizontal",
                       callback=self.RetrieveFilesList)
        self.retryButton = OWGUI.button(box, self, "Retry",
                                        callback=self.RetrieveFilesList)
        self.retryButton.hide()
        box = OWGUI.widgetBox(self.controlArea, orientation="horizontal")
        OWGUI.rubber(box)
        if wantCloseButton:
            OWGUI.button(box, self, "Close",
                         callback=self.accept,
                         tooltip="Close")

        self.infoLabel = QLabel()
        self.infoLabel.setAlignment(Qt.AlignCenter)

        self.controlArea.layout().addWidget(self.infoLabel)
        self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.updateItems = []

        self.resize(800, 600)

        self.progress = ProgressState(self, maximum=3)
        self.progress.valueChanged.connect(self._updateProgress)
        self.progress.rangeChanged.connect(self._updateProgress)
        self.executor = ThreadExecutor(
            threadPool=QThreadPool(maxThreadCount=2)
        )

        task = Task(self, function=self.RetrieveFilesList)
        task.exceptionReady.connect(self.HandleError)
        task.start()

        self._tasks = []
        self._haveProgress = False

    def RetrieveFilesList(self):
        self.progress.setRange(0, 3)
        self.serverFiles = serverfiles.ServerFiles(access_code=self.accessCode)

        task = Task(function=partial(retrieveFilesList, self.serverFiles,
                                     self.domains,
                                     methodinvoke(self.progress, "advance")))

        task.resultReady.connect(self.SetFilesList)
        task.exceptionReady.connect(self.HandleError)

        self.executor.submit(task)

        self.setEnabled(False)

    def SetFilesList(self, serverInfo):
        """
        Set the files to show.
        """
        self.setEnabled(True)

        domains = serverInfo.keys()
        if not domains:
            if self.domains:
                domains = self.domains
            else:
                domains = serverfiles.listdomains()

        localInfo = dict([(dom, serverfiles.allinfo(dom)) for dom in domains])

        all_tags = set()

        self.filesView.clear()
        self.updateItems = []

        for item in join_info_dict(localInfo, serverInfo):
            tree_item = UpdateTreeWidgetItem(item)
            options_widget = UpdateOptionsWidget(item.state)
            options_widget.item = item

            options_widget.installClicked.connect(
                partial(self.SubmitDownloadTask, item.domain, item.filename)
            )
            options_widget.removeClicked.connect(
                partial(self.SubmitRemoveTask, item.domain, item.filename)
            )

            self.updateItems.append((item, tree_item, options_widget))
            all_tags.update(item.tags)

        self.filesView.addTopLevelItems(
            [tree_item for _, tree_item, _ in self.updateItems]
        )

        for item, tree_item, options_widget in self.updateItems:
            self.filesView.setItemWidget(tree_item, 0, options_widget)

            # Add an update button if the file is updateable
            if item.state == OUTDATED:
                button = QToolButton(
                    None, text="Update",
                    maximumWidth=120,
                    maximumHeight=30
                )

                if sys.platform == "darwin":
                    button.setAttribute(Qt.WA_MacSmallSize)

                button.clicked.connect(
                    partial(self.SubmitDownloadTask, item.domain,
                            item.filename)
                )

                self.filesView.setItemWidget(tree_item, 2, button)

        self.progress.advance()

        self.filesView.setColumnWidth(0, self.filesView.sizeHintForColumn(0))

        for column in range(1, 4):
            contents_hint = self.filesView.sizeHintForColumn(column)
            header_hint = self.filesView.header().sectionSizeHint(column)
            width = max(min(contents_hint, 400), header_hint)
            self.filesView.setColumnWidth(column, width)

        self.lineEditFilter.setItems([hint for hint in sorted(all_tags)
                                      if not hint.startswith("#")])
        self.SearchUpdate()
        self.UpdateInfoLabel()
        self.toggleButtons()
        self.cancelButton.setEnabled(False)

        self.progress.setRange(0, 0)

    def buttonCheck(self, selected_items, state, button):
        for item in selected_items:
            if item.state != state:
                button.setEnabled(False)
            else:
                button.setEnabled(True)
                break

    def toggleButtons(self):
        selected_items = [item for item, tree_item, _ in self.updateItems if not tree_item.isHidden()]
        self.buttonCheck(selected_items, OUTDATED, self.updateButton)
        self.buttonCheck(selected_items, AVAILABLE, self.downloadButton)

    def HandleError(self, exception):
        if isinstance(exception, IOError):
            self.error(0,
                       "Could not connect to server! Press the Retry "
                       "button to try again.")
            self.SetFilesList({})
        else:
            sys.excepthook(type(exception), exception.args, None)
            self.progress.setRange(0, 0)
            self.setEnabled(True)

    def UpdateInfoLabel(self):
        local = [item for item, tree_item, _ in self.updateItems
                 if item.state != AVAILABLE and not tree_item.isHidden() ]
        size = sum(float(item.size) for item in local)

        onServer = [item for item, tree_item, _ in self.updateItems if not tree_item.isHidden()]
        sizeOnServer = sum(float(item.size) for item in onServer)

        text = ("%i items, %s (on server: %i items, %s)" %
                (len(local),
                 sizeof_fmt(size),
                 len(onServer),
                 sizeof_fmt(sizeOnServer)))

        self.infoLabel.setText(text)

    def UpdateAll(self):
        for item, tree_item, _ in self.updateItems:
            if item.state == OUTDATED and not tree_item.isHidden():
                self.SubmitDownloadTask(item.domain, item.filename)

    def DownloadFiltered(self):
        # TODO: submit items in the order shown.
        for item, tree_item, _ in self.updateItems:
            if not tree_item.isHidden() and item.state in \
                    [AVAILABLE, OUTDATED]:
                self.SubmitDownloadTask(item.domain, item.filename)

    def SearchUpdate(self, searchString=None):
        strings = unicode(self.lineEditFilter.text()).split()
        for item, tree_item, _ in self.updateItems:
            hide = not all(UpdateItem_match(item, string)
                           for string in strings)
            tree_item.setHidden(hide)
        self.UpdateInfoLabel()
        self.toggleButtons()

    def SubmitDownloadTask(self, domain, filename):
        """
        Submit the (domain, filename) to be downloaded/updated.
        """
        self.cancelButton.setEnabled(True)

        index = self.updateItemIndex(domain, filename)
        _, tree_item, opt_widget = self.updateItems[index]

        if self.accessCode:
            sf = serverfiles.ServerFiles(access_code=self.accessCode)
        else:
            sf = serverfiles.ServerFiles()

        task = DownloadTask(domain, filename, sf)

        self.executor.submit(task)

        self.progress.adjustRange(0, 100)

        pb = ItemProgressBar(self.filesView)
        pb.setRange(0, 100)
        pb.setTextVisible(False)

        task.advanced.connect(pb.advance)
        task.advanced.connect(self.progress.advance)
        task.finished.connect(pb.hide)
        task.finished.connect(self.onDownloadFinished, Qt.QueuedConnection)
        task.exception.connect(self.onDownloadError, Qt.QueuedConnection)

        self.filesView.setItemWidget(tree_item, 2, pb)

        # Clear the text so it does not show behind the progress bar.
        tree_item.setData(2, Qt.DisplayRole, "")
        pb.show()

        # Disable the options widget
        opt_widget.setEnabled(False)
        self._tasks.append(task)

    def EndDownloadTask(self, task):
        future = task.future()
        index = self.updateItemIndex(task.domain, task.filename)
        item, tree_item, opt_widget = self.updateItems[index]

        self.filesView.removeItemWidget(tree_item, 2)
        opt_widget.setEnabled(True)

        if future.cancelled():
            # Restore the previous state
            tree_item.setUpdateItem(item)
            opt_widget.setState(item.state)

        elif future.exception():
            tree_item.setUpdateItem(item)
            opt_widget.setState(item.state)

            # Show the exception string in the size column.
            tree_item.setData(2, Qt.DisplayRole,
                         QVariant("Error occurred while downloading:" +
                                  str(future.exception())))

        else:
            # get the new updated info dict and replace the the old item
            info = serverfiles.info(item.domain, item.filename)
            new_item = update_item_from_info(item.domain, item.filename,
                                             info, info)

            self.updateItems[index] = (new_item, tree_item, opt_widget)

            tree_item.setUpdateItem(new_item)
            opt_widget.setState(new_item.state)

            self.UpdateInfoLabel()

    def SubmitRemoveTask(self, domain, filename):
        serverfiles.remove(domain, filename)
        index = self.updateItemIndex(domain, filename)
        item, tree_item, opt_widget = self.updateItems[index]

        if item.info_server:
            new_item = item._replace(state=AVAILABLE, local=None,
                                      info_local=None)
        else:
            new_item = item._replace(local=None, info_local=None)
            # Disable the options widget. No more actions can be performed
            # for the item.
            opt_widget.setEnabled(False)

        tree_item.setUpdateItem(new_item)
        opt_widget.setState(new_item.state)
        self.updateItems[index] = (new_item, tree_item, opt_widget)

        self.UpdateInfoLabel()

    def Cancel(self):
        """
        Cancel all pending update/download tasks (that have not yet started).
        """
        for task in self._tasks:
            task.future().cancel()

    def onDeleteWidget(self):
        self.Cancel()
        self.executor.shutdown(wait=False)
        OWBaseWidget.onDeleteWidget(self)

    def onDownloadFinished(self):
        # on download completed/canceled/error
        assert QThread.currentThread() is self.thread()
        for task in list(self._tasks):
            future = task.future()
            if future.done():
                self.EndDownloadTask(task)
                self._tasks.remove(task)

        if not self._tasks:
            # Clear/reset the overall progress
            self.progress.setRange(0, 0)

            self.cancelButton.setEnabled(False)

    def onDownloadError(self, exc_info):
        sys.excepthook(*exc_info)

    def updateItemIndex(self, domain, filename):
        for i, (item, _, _) in enumerate(self.updateItems):
            if item.domain == domain and item.filename == filename:
                return i
        raise ValueError("%r, %r not in update list" % (domain, filename))

    def _updateProgress(self, *args):
        rmin, rmax = self.progress.range()
        if rmin != rmax:
            if not self._haveProgress:
                self._haveProgress = True
                self.progressBarInit()

            self.progressBarSet(self.progress.ratioCompleted() * 100,
                                processEventsFlags=None)
        if rmin == rmax:
            self._haveProgress = False
            self.progressBarFinished()