source: orange/Orange/OrangeWidgets/OWDatabasesUpdate.py @ 10979:045af49ad3e6

Revision 10979:045af49ad3e6, 18.9 KB checked in by markotoplak, 20 months ago (diff)

Fixed a typo in OWDatabasesUpdate.

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