source: orange/Orange/OrangeWidgets/OWDatabasesUpdate.py @ 11436:3e57927834a2

Revision 11436:3e57927834a2, 16.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Code style fixes for 'OWDatabasesUpdate'.

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