source: orange/Orange/OrangeWidgets/OWDatabasesUpdate.py @ 11473:2abb476eaf6a

Revision 11473:2abb476eaf6a, 16.5 KB checked in by markotoplak, 12 months ago (diff)

OWDatabaseUpdate: changed some strings.

RevLine 
[11436]1from __future__ import with_statement
2
3import os
4import sys
5
6from datetime import datetime
7
[11437]8import Orange
[11436]9
[11437]10from Orange.utils import serverfiles, environ
[11436]11from Orange.utils.serverfiles import sizeformat as sizeof_fmt
12
[8042]13from OWWidget import *
[11436]14from OWConcurrent import *
[8042]15
[11436]16import OWGUIEx
[8042]17
18
19class ItemProgressBar(QProgressBar):
[11437]20    """Progress Bar with and `advance()` slot.
[11436]21    """
[8042]22    @pyqtSignature("advance()")
23    def advance(self):
24        self.setValue(self.value() + 1)
[11436]25
26
[8042]27class ProgressBarRedirect(QObject):
28    def __init__(self, parent, redirect):
29        QObject.__init__(self, parent)
30        self.redirect = redirect
31        self._delay = False
[11436]32
[8042]33    @pyqtSignature("advance()")
34    def advance(self):
[11436]35        # delay OWBaseWidget.progressBarSet call, because it calls
36        # qApp.processEvents which can result in 'event queue climbing'
37        # and max. recursion error if GUI thread gets another advance
38        # signal before it finishes with this one
[8042]39        if not self._delay:
40            try:
41                self._delay = True
42                self.redirect.advance()
43            finally:
44                self._delay = False
45        else:
46            QTimer.singleShot(10, self.advance)
47
[11437]48_icons_dir = os.path.join(environ.canvas_install_dir, "icons")
[11436]49
50
51def icon(name):
52    return QIcon(os.path.join(_icons_dir, name))
53
54
[8042]55class UpdateOptionsWidget(QWidget):
[11437]56    """
57    A Widget with download/update/remove options.
58    """
[8042]59    def __init__(self, updateCallback, removeCallback, state, *args):
60        QWidget.__init__(self, *args)
61        self.updateCallback = updateCallback
62        self.removeCallback = removeCallback
63        layout = QHBoxLayout()
64        layout.setSpacing(1)
65        layout.setContentsMargins(1, 1, 1, 1)
66        self.updateButton = QToolButton(self)
[11436]67        self.updateButton.setIcon(icon("update.png"))
[8042]68        self.updateButton.setToolTip("Download")
[11436]69
[8042]70        self.removeButton = QToolButton(self)
[11436]71        self.removeButton.setIcon(icon("delete.png"))
[8042]72        self.removeButton.setToolTip("Remove from system")
[11436]73
74        self.connect(self.updateButton, SIGNAL("released()"),
75                     self.updateCallback)
76        self.connect(self.removeButton, SIGNAL("released()"),
77                     self.removeCallback)
78
[8042]79        self.setMaximumHeight(30)
80        layout.addWidget(self.updateButton)
81        layout.addWidget(self.removeButton)
82        self.setLayout(layout)
83        self.SetState(state)
84
85    def SetState(self, state):
86        self.state = state
87        if state == 0:
[11436]88            self.updateButton.setIcon(icon("update1.png"))
[8042]89            self.updateButton.setToolTip("Update")
90            self.updateButton.setEnabled(False)
91            self.removeButton.setEnabled(True)
92        elif state == 1:
[11436]93            self.updateButton.setIcon(icon("update1.png"))
[8042]94            self.updateButton.setToolTip("Update")
95            self.updateButton.setEnabled(True)
96            self.removeButton.setEnabled(True)
97        elif state == 2:
[11436]98            self.updateButton.setIcon(icon("update.png"))
[8042]99            self.updateButton.setToolTip("Download")
100            self.updateButton.setEnabled(True)
101            self.removeButton.setEnabled(False)
102        elif state == 3:
[11436]103            self.updateButton.setIcon(icon("update.png"))
[8042]104            self.updateButton.setToolTip("")
105            self.updateButton.setEnabled(False)
106            self.removeButton.setEnabled(True)
[11437]107        else:
108            raise ValueError("Invalid state %r" % state)
[8042]109
110
111class UpdateTreeWidgetItem(QTreeWidgetItem):
[11436]112    stateDict = {0: "up-to-date",
113                 1: "new version available",
114                 2: "not downloaded",
115                 3: "obsolete"}
116
117    def __init__(self, master, treeWidget, domain, filename, infoLocal,
118                 infoServer, *args):
[11437]119        dateServer = dateLocal = None
120        if infoServer:
121            dateServer = datetime.strptime(
122                infoServer["datetime"].split(".")[0], "%Y-%m-%d %H:%M:%S"
123            )
124        if infoLocal:
125            dateLocal = datetime.strptime(
126                infoLocal["datetime"].split(".")[0], "%Y-%m-%d %H:%M:%S"
127            )
[8042]128        if not infoLocal:
129            self.state = 2
130        elif not infoServer:
131            self.state = 3
132        else:
133            self.state = 0 if dateLocal >= dateServer else 1
[11436]134
[8042]135        title = infoServer["title"] if infoServer else (infoLocal["title"])
136        tags = infoServer["tags"] if infoServer else infoLocal["tags"]
[11436]137        specialTags = dict([tuple(tag.split(":"))
138                            for tag in tags
139                            if tag.startswith("#") and ":" in tag])
[8042]140        tags = ", ".join(tag for tag in tags if not tag.startswith("#"))
141        self.size = infoServer["size"] if infoServer else infoLocal["size"]
[11436]142
[8042]143        size = sizeof_fmt(float(self.size))
[11436]144        state = self.stateDict[self.state]
145        if self.state == 1:
146            state += dateServer.strftime(" (%Y, %b, %d)")
147
[8042]148        QTreeWidgetItem.__init__(self, treeWidget, ["", title, size])
[11437]149        if dateServer is not None:
150            self.setData(3, Qt.DisplayRole,
151                         dateServer.date().isoformat())
152
[11436]153        self.updateWidget = UpdateOptionsWidget(
154            self.StartDownload, self.Remove, self.state, treeWidget
155        )
156
[8042]157        self.treeWidget().setItemWidget(self, 0, self.updateWidget)
158        self.updateWidget.show()
159        self.master = master
160        self.title = title
161        self.tags = tags.split(", ")
162        self.specialTags = specialTags
163        self.domain = domain
164        self.filename = filename
165        self.UpdateToolTip()
166
167    def UpdateToolTip(self):
[11473]168        state = {0: "downloaded, current",
169                 1: "downloaded, needs update",
170                 2: "not downloaded",
[11436]171                 3: "obsolete"}
172        tooltip = "State: %s\nTags: %s" % (state[self.state],
173                                           ", ".join(self.tags))
[11473]174
[8042]175        if self.state != 2:
[11437]176            tooltip += ("\nFile: %s" %
177                        serverfiles.localpath(self.domain, self.filename))
[8042]178        for i in range(1, 5):
179            self.setToolTip(i, tooltip)
[11436]180
[8042]181    def StartDownload(self):
182        self.updateWidget.removeButton.setEnabled(False)
183        self.updateWidget.updateButton.setEnabled(False)
184        self.setData(2, Qt.DisplayRole, QVariant(""))
[11437]185        serverFiles = serverfiles.ServerFiles(
[11436]186            access_code=self.master.accessCode if self.master.accessCode
187            else None
188        )
189
[8042]190        pb = ItemProgressBar(self.treeWidget())
191        pb.setRange(0, 100)
192        pb.setTextVisible(False)
[11436]193
[8042]194        self.task = AsyncCall(threadPool=QThreadPool.globalInstance())
[11436]195
[8042]196        if not getattr(self.master, "_sum_progressBar", None):
[11436]197            self.master._sum_progressBar = OWGUI.ProgressBar(self.master, 0)
[8042]198            self.master._sum_progressBar.in_progress = 0
199        master_pb = self.master._sum_progressBar
200        master_pb.iter += 100
201        master_pb.in_progress += 1
[11436]202        self._progressBarRedirect = \
203            ProgressBarRedirect(QThread.currentThread(), master_pb)
204        QObject.connect(self.task,
205                        SIGNAL("advance()"),
206                        pb.advance,
207                        Qt.QueuedConnection)
208        QObject.connect(self.task,
209                        SIGNAL("advance()"),
210                        self._progressBarRedirect.advance,
211                        Qt.QueuedConnection)
212        QObject.connect(self.task,
213                        SIGNAL("finished(QString)"),
214                        self.EndDownload,
215                        Qt.QueuedConnection)
[8042]216        self.treeWidget().setItemWidget(self, 2, pb)
217        pb.show()
[11436]218
[11437]219        self.task.apply_async(serverfiles.download,
[11436]220                              args=(self.domain, self.filename, serverFiles),
221                              kwargs=dict(callback=self.task.emitAdvance))
[8042]222
223    def EndDownload(self, exitCode=0):
224        self.treeWidget().removeItemWidget(self, 2)
225        if str(exitCode) == "Ok":
226            self.state = 0
227            self.updateWidget.SetState(self.state)
[11436]228            self.setData(2, Qt.DisplayRole,
229                         QVariant(sizeof_fmt(float(self.size))))
[8042]230            self.master.UpdateInfoLabel()
231            self.UpdateToolTip()
232        else:
233            self.updateWidget.SetState(1)
[11436]234            self.setData(2, Qt.DisplayRole,
235                         QVariant("Error occurred while downloading:" +
236                                  str(exitCode)))
237
[8042]238        master_pb = self.master._sum_progressBar
[11436]239
[8042]240        if master_pb and master_pb.in_progress == 1:
241            master_pb.finish()
242            self.master._sum_progressBar = None
243        elif master_pb:
244            master_pb.in_progress -= 1
[11436]245
[8042]246    def Remove(self):
[11437]247        serverfiles.remove(self.domain, self.filename)
[8042]248        self.state = 2
249        self.updateWidget.SetState(self.state)
250        self.master.UpdateInfoLabel()
251        self.UpdateToolTip()
252
253    def __contains__(self, item):
[11436]254        return any(item.lower() in tag.lower()
255                   for tag in self.tags + [self.title])
256
[9305]257    def __lt__(self, other):
[11436]258        return getattr(self, "title", "") < getattr(other, "title", "")
259
[8042]260
261class UpdateItemDelegate(QItemDelegate):
262    def sizeHint(self, option, index):
263        size = QItemDelegate.sizeHint(self, option, index)
264        parent = self.parent()
265        item = parent.itemFromIndex(index)
266        widget = parent.itemWidget(item, 0)
267        if widget:
[11436]268            size = QSize(size.width(), widget.sizeHint().height() / 2)
[8042]269        return size
[11436]270
271
[8042]272def retrieveFilesList(serverFiles, domains=None, advance=lambda: None):
[11436]273    """
274    Retrieve and return serverfiles.allinfo for all domains.
275    """
[8042]276    domains = serverFiles.listdomains() if domains is None else domains
277    advance()
278    serverInfo = dict([(dom, serverFiles.allinfo(dom)) for dom in domains])
279    advance()
280    return serverInfo
[11436]281
282
[8042]283class OWDatabasesUpdate(OWWidget):
[11436]284    def __init__(self, parent=None, signalManager=None,
285                 name="Databases update", wantCloseButton=False,
286                 searchString="", showAll=True, domains=None,
287                 accessCode=""):
[8042]288        OWWidget.__init__(self, parent, signalManager, name)
289        self.searchString = searchString
290        self.accessCode = accessCode
291        self.showAll = showAll
292        self.domains = domains
[11437]293        self.serverFiles = serverfiles.ServerFiles()
[8042]294        box = OWGUI.widgetBox(self.mainArea, orientation="horizontal")
[11436]295
296        self.lineEditFilter = \
297            OWGUIEx.lineEditHint(box, self, "searchString", "Filter",
298                                 caseSensitive=False,
299                                 delimiters=" ",
300                                 matchAnywhere=True,
301                                 listUpdateCallback=self.SearchUpdate,
302                                 callbackOnType=True,
303                                 callback=self.SearchUpdate)
[8042]304
305        box = OWGUI.widgetBox(self.mainArea, "Files")
306        self.filesView = QTreeWidget(self)
[11437]307        self.filesView.setHeaderLabels(["Options", "Title", "Size",
308                                        "Last Updated"])
[8042]309        self.filesView.setRootIsDecorated(False)
310        self.filesView.setSelectionMode(QAbstractItemView.NoSelection)
311        self.filesView.setSortingEnabled(True)
312        self.filesView.setItemDelegate(UpdateItemDelegate(self.filesView))
[11436]313        self.connect(self.filesView.model(),
314                     SIGNAL("layoutChanged()"),
315                     self.SearchUpdate)
[8042]316        box.layout().addWidget(self.filesView)
317
318        box = OWGUI.widgetBox(self.mainArea, orientation="horizontal")
[11436]319        OWGUI.button(box, self, "Update all local files",
320                     callback=self.UpdateAll,
321                     tooltip="Update all updatable files")
322        OWGUI.button(box, self, "Download filtered",
323                     callback=self.DownloadFiltered,
324                     tooltip="Download all filtered files shown")
[8042]325        OWGUI.rubber(box)
[11436]326        OWGUI.lineEdit(box, self, "accessCode", "Access Code",
327                       orientation="horizontal",
328                       callback=self.RetrieveFilesList)
329        self.retryButton = OWGUI.button(box, self, "Retry",
330                                        callback=self.RetrieveFilesList)
[8042]331        self.retryButton.hide()
332        box = OWGUI.widgetBox(self.mainArea, orientation="horizontal")
333        OWGUI.rubber(box)
334        if wantCloseButton:
[11436]335            OWGUI.button(box, self, "Close",
336                         callback=self.accept,
337                         tooltip="Close")
[8042]338
339        self.infoLabel = QLabel()
340        self.infoLabel.setAlignment(Qt.AlignCenter)
[11436]341
[8042]342        self.mainArea.layout().addWidget(self.infoLabel)
343        self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
344
345        self.updateItems = []
346        self.allTags = []
[11436]347
[8042]348        self.resize(800, 600)
[11436]349
[8042]350        QTimer.singleShot(50, self.RetrieveFilesList)
[11436]351
[8042]352    def RetrieveFilesList(self):
[11437]353        self.serverFiles = serverfiles.ServerFiles(access_code=self.accessCode)
[8042]354        self.pb = ProgressBar(self, 3)
[11436]355        self.async_retrieve = createTask(retrieveFilesList,
356                                         (self.serverFiles, self.domains,
357                                          self.pb.advance),
358                                         onResult=self.SetFilesList,
359                                         onError=self.HandleError)
360
[8042]361        self.setEnabled(False)
[11436]362
[8042]363    def SetFilesList(self, serverInfo):
364        self.setEnabled(True)
[11437]365        domains = serverInfo.keys() or serverfiles.listdomains()
366        localInfo = dict([(dom, serverfiles.allinfo(dom))
[11436]367                          for dom in domains])
[8042]368        items = []
[11436]369
[8042]370        self.allTags = set()
371        allTitles = set()
372        self.updateItems = []
[11436]373
374        for domain in set(domains) - set(["test", "demo"]):
375            local = localInfo.get(domain, {})
376            server = serverInfo.get(domain, {})
[8042]377            files = sorted(set(server.keys() + local.keys()))
[11436]378            for filename in files:
379                infoServer = server.get(filename, None)
380                infoLocal = local.get(filename, None)
381
382                items.append((self.filesView, domain, filename, infoLocal,
383                              infoServer))
384
[8042]385                displayInfo = infoServer if infoServer else infoLocal
386                self.allTags.update(displayInfo["tags"])
387                allTitles.update(displayInfo["title"].split())
[11436]388
389        for item in items:
[8042]390            self.updateItems.append(UpdateTreeWidgetItem(self, *item))
391        self.pb.advance()
[11437]392        for column in range(4):
393            whint = self.filesView.sizeHintForColumn(column)
394            width = min(whint, 400)
395            self.filesView.setColumnWidth(column, width)
396
[11436]397        self.lineEditFilter.setItems([hint for hint in sorted(self.allTags)
398                                      if not hint.startswith("#")])
[8042]399        self.SearchUpdate()
400        self.UpdateInfoLabel()
401        self.pb.finish()
[11436]402
[8042]403    def HandleError(self, (exc_type, exc_value, tb)):
404        if exc_type >= IOError:
[11436]405            self.error(0,
406                       "Could not connect to server! Press the Retry "
407                       "button to try again.")
[8042]408            self.SetFilesList({})
409        else:
410            sys.excepthook(exc_type, exc_value, tb)
411            self.pb.finish()
412            self.setEnabled(True)
413
414    def UpdateInfoLabel(self):
415        local = [item for item in self.updateItems if item.state != 2]
416        onServer = [item for item in self.updateItems]
[11436]417        size = sum(float(item.specialTags.get("#uncompressed", item.size))
418                   for item in local)
419        sizeOnServer = sum(float(item.size) for item in self.updateItems)
420
[8042]421        if self.showAll:
[11436]422
423            text = ("%i items, %s (data on server: %i items, %s)" %
424                    (len(local),
425                     sizeof_fmt(size),
426                     len(onServer),
427                     sizeof_fmt(sizeOnServer)))
[8042]428        else:
[11436]429            text = "%i items, %s" % (len(local), sizeof_fmt(size))
430
431        self.infoLabel.setText(text)
432
[8042]433    def UpdateAll(self):
434        for item in self.updateItems:
435            if item.state == 1:
436                item.StartDownload()
[11436]437
[8042]438    def DownloadFiltered(self):
439        for item in self.updateItems:
440            if not item.isHidden() and item.state != 0:
441                item.StartDownload()
442
443    def SearchUpdate(self, searchString=None):
[11436]444        strings = unicode(self.lineEditFilter.text()).split()
[8042]445        tags = set()
446        for item in self.updateItems:
447            hide = not all(str(string) in item for string in strings)
448            item.setHidden(hide)
449            if not hide:
450                tags.update(item.tags)
[11436]451
452
[8042]453if __name__ == "__main__":
454    app = QApplication(sys.argv)
455    w = OWDatabasesUpdate(wantCloseButton=True)
456    w.show()
457    w.exec_()
Note: See TracBrowser for help on using the repository browser.