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.

Line 
1from __future__ import with_statement
2
3import os
4import sys
5
6from datetime import datetime
7
8import Orange
9
10from Orange.utils import serverfiles, environ
11from Orange.utils.serverfiles import sizeformat as sizeof_fmt
12
13from OWWidget import *
14from OWConcurrent import *
15
16import OWGUIEx
17
18
19class ItemProgressBar(QProgressBar):
20    """Progress Bar with and `advance()` slot.
21    """
22    @pyqtSignature("advance()")
23    def advance(self):
24        self.setValue(self.value() + 1)
25
26
27class ProgressBarRedirect(QObject):
28    def __init__(self, parent, redirect):
29        QObject.__init__(self, parent)
30        self.redirect = redirect
31        self._delay = False
32
33    @pyqtSignature("advance()")
34    def advance(self):
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
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
48_icons_dir = os.path.join(environ.canvas_install_dir, "icons")
49
50
51def icon(name):
52    return QIcon(os.path.join(_icons_dir, name))
53
54
55class UpdateOptionsWidget(QWidget):
56    """
57    A Widget with download/update/remove options.
58    """
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)
67        self.updateButton.setIcon(icon("update.png"))
68        self.updateButton.setToolTip("Download")
69
70        self.removeButton = QToolButton(self)
71        self.removeButton.setIcon(icon("delete.png"))
72        self.removeButton.setToolTip("Remove from system")
73
74        self.connect(self.updateButton, SIGNAL("released()"),
75                     self.updateCallback)
76        self.connect(self.removeButton, SIGNAL("released()"),
77                     self.removeCallback)
78
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:
88            self.updateButton.setIcon(icon("update1.png"))
89            self.updateButton.setToolTip("Update")
90            self.updateButton.setEnabled(False)
91            self.removeButton.setEnabled(True)
92        elif state == 1:
93            self.updateButton.setIcon(icon("update1.png"))
94            self.updateButton.setToolTip("Update")
95            self.updateButton.setEnabled(True)
96            self.removeButton.setEnabled(True)
97        elif state == 2:
98            self.updateButton.setIcon(icon("update.png"))
99            self.updateButton.setToolTip("Download")
100            self.updateButton.setEnabled(True)
101            self.removeButton.setEnabled(False)
102        elif state == 3:
103            self.updateButton.setIcon(icon("update.png"))
104            self.updateButton.setToolTip("")
105            self.updateButton.setEnabled(False)
106            self.removeButton.setEnabled(True)
107        else:
108            raise ValueError("Invalid state %r" % state)
109
110
111class UpdateTreeWidgetItem(QTreeWidgetItem):
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):
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            )
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
134
135        title = infoServer["title"] if infoServer else (infoLocal["title"])
136        tags = infoServer["tags"] if infoServer else infoLocal["tags"]
137        specialTags = dict([tuple(tag.split(":"))
138                            for tag in tags
139                            if tag.startswith("#") and ":" in tag])
140        tags = ", ".join(tag for tag in tags if not tag.startswith("#"))
141        self.size = infoServer["size"] if infoServer else infoLocal["size"]
142
143        size = sizeof_fmt(float(self.size))
144        state = self.stateDict[self.state]
145        if self.state == 1:
146            state += dateServer.strftime(" (%Y, %b, %d)")
147
148        QTreeWidgetItem.__init__(self, treeWidget, ["", title, size])
149        if dateServer is not None:
150            self.setData(3, Qt.DisplayRole,
151                         dateServer.date().isoformat())
152
153        self.updateWidget = UpdateOptionsWidget(
154            self.StartDownload, self.Remove, self.state, treeWidget
155        )
156
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):
168        state = {0: "downloaded, current",
169                 1: "downloaded, needs update",
170                 2: "not downloaded",
171                 3: "obsolete"}
172        tooltip = "State: %s\nTags: %s" % (state[self.state],
173                                           ", ".join(self.tags))
174
175        if self.state != 2:
176            tooltip += ("\nFile: %s" %
177                        serverfiles.localpath(self.domain, self.filename))
178        for i in range(1, 5):
179            self.setToolTip(i, tooltip)
180
181    def StartDownload(self):
182        self.updateWidget.removeButton.setEnabled(False)
183        self.updateWidget.updateButton.setEnabled(False)
184        self.setData(2, Qt.DisplayRole, QVariant(""))
185        serverFiles = serverfiles.ServerFiles(
186            access_code=self.master.accessCode if self.master.accessCode
187            else None
188        )
189
190        pb = ItemProgressBar(self.treeWidget())
191        pb.setRange(0, 100)
192        pb.setTextVisible(False)
193
194        self.task = AsyncCall(threadPool=QThreadPool.globalInstance())
195
196        if not getattr(self.master, "_sum_progressBar", None):
197            self.master._sum_progressBar = OWGUI.ProgressBar(self.master, 0)
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
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)
216        self.treeWidget().setItemWidget(self, 2, pb)
217        pb.show()
218
219        self.task.apply_async(serverfiles.download,
220                              args=(self.domain, self.filename, serverFiles),
221                              kwargs=dict(callback=self.task.emitAdvance))
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)
228            self.setData(2, Qt.DisplayRole,
229                         QVariant(sizeof_fmt(float(self.size))))
230            self.master.UpdateInfoLabel()
231            self.UpdateToolTip()
232        else:
233            self.updateWidget.SetState(1)
234            self.setData(2, Qt.DisplayRole,
235                         QVariant("Error occurred while downloading:" +
236                                  str(exitCode)))
237
238        master_pb = self.master._sum_progressBar
239
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
245
246    def Remove(self):
247        serverfiles.remove(self.domain, self.filename)
248        self.state = 2
249        self.updateWidget.SetState(self.state)
250        self.master.UpdateInfoLabel()
251        self.UpdateToolTip()
252
253    def __contains__(self, item):
254        return any(item.lower() in tag.lower()
255                   for tag in self.tags + [self.title])
256
257    def __lt__(self, other):
258        return getattr(self, "title", "") < getattr(other, "title", "")
259
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:
268            size = QSize(size.width(), widget.sizeHint().height() / 2)
269        return size
270
271
272def retrieveFilesList(serverFiles, domains=None, advance=lambda: None):
273    """
274    Retrieve and return serverfiles.allinfo for all domains.
275    """
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
281
282
283class OWDatabasesUpdate(OWWidget):
284    def __init__(self, parent=None, signalManager=None,
285                 name="Databases update", wantCloseButton=False,
286                 searchString="", showAll=True, domains=None,
287                 accessCode=""):
288        OWWidget.__init__(self, parent, signalManager, name)
289        self.searchString = searchString
290        self.accessCode = accessCode
291        self.showAll = showAll
292        self.domains = domains
293        self.serverFiles = serverfiles.ServerFiles()
294        box = OWGUI.widgetBox(self.mainArea, orientation="horizontal")
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)
304
305        box = OWGUI.widgetBox(self.mainArea, "Files")
306        self.filesView = QTreeWidget(self)
307        self.filesView.setHeaderLabels(["Options", "Title", "Size",
308                                        "Last Updated"])
309        self.filesView.setRootIsDecorated(False)
310        self.filesView.setSelectionMode(QAbstractItemView.NoSelection)
311        self.filesView.setSortingEnabled(True)
312        self.filesView.setItemDelegate(UpdateItemDelegate(self.filesView))
313        self.connect(self.filesView.model(),
314                     SIGNAL("layoutChanged()"),
315                     self.SearchUpdate)
316        box.layout().addWidget(self.filesView)
317
318        box = OWGUI.widgetBox(self.mainArea, orientation="horizontal")
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")
325        OWGUI.rubber(box)
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)
331        self.retryButton.hide()
332        box = OWGUI.widgetBox(self.mainArea, orientation="horizontal")
333        OWGUI.rubber(box)
334        if wantCloseButton:
335            OWGUI.button(box, self, "Close",
336                         callback=self.accept,
337                         tooltip="Close")
338
339        self.infoLabel = QLabel()
340        self.infoLabel.setAlignment(Qt.AlignCenter)
341
342        self.mainArea.layout().addWidget(self.infoLabel)
343        self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
344
345        self.updateItems = []
346        self.allTags = []
347
348        self.resize(800, 600)
349
350        QTimer.singleShot(50, self.RetrieveFilesList)
351
352    def RetrieveFilesList(self):
353        self.serverFiles = serverfiles.ServerFiles(access_code=self.accessCode)
354        self.pb = ProgressBar(self, 3)
355        self.async_retrieve = createTask(retrieveFilesList,
356                                         (self.serverFiles, self.domains,
357                                          self.pb.advance),
358                                         onResult=self.SetFilesList,
359                                         onError=self.HandleError)
360
361        self.setEnabled(False)
362
363    def SetFilesList(self, serverInfo):
364        self.setEnabled(True)
365        domains = serverInfo.keys() or serverfiles.listdomains()
366        localInfo = dict([(dom, serverfiles.allinfo(dom))
367                          for dom in domains])
368        items = []
369
370        self.allTags = set()
371        allTitles = set()
372        self.updateItems = []
373
374        for domain in set(domains) - set(["test", "demo"]):
375            local = localInfo.get(domain, {})
376            server = serverInfo.get(domain, {})
377            files = sorted(set(server.keys() + local.keys()))
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
385                displayInfo = infoServer if infoServer else infoLocal
386                self.allTags.update(displayInfo["tags"])
387                allTitles.update(displayInfo["title"].split())
388
389        for item in items:
390            self.updateItems.append(UpdateTreeWidgetItem(self, *item))
391        self.pb.advance()
392        for column in range(4):
393            whint = self.filesView.sizeHintForColumn(column)
394            width = min(whint, 400)
395            self.filesView.setColumnWidth(column, width)
396
397        self.lineEditFilter.setItems([hint for hint in sorted(self.allTags)
398                                      if not hint.startswith("#")])
399        self.SearchUpdate()
400        self.UpdateInfoLabel()
401        self.pb.finish()
402
403    def HandleError(self, (exc_type, exc_value, tb)):
404        if exc_type >= IOError:
405            self.error(0,
406                       "Could not connect to server! Press the Retry "
407                       "button to try again.")
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]
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
421        if self.showAll:
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)))
428        else:
429            text = "%i items, %s" % (len(local), sizeof_fmt(size))
430
431        self.infoLabel.setText(text)
432
433    def UpdateAll(self):
434        for item in self.updateItems:
435            if item.state == 1:
436                item.StartDownload()
437
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):
444        strings = unicode(self.lineEditFilter.text()).split()
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)
451
452
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.