Changeset 11484:180ff4d9e42e in orange


Ignore:
Timestamp:
04/30/13 10:57:32 (12 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

A major rewrite of the 'Databases Update' widget.

Now has better separation between gui and the update logic, the initialization
is significantly faster and the update items can be properly sorted.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeWidgets/OWDatabasesUpdate.py

    r11483 r11484  
    66from datetime import datetime 
    77from functools import partial 
     8from collections import namedtuple 
    89 
    910from PyQt4.QtCore import pyqtSignal as Signal, pyqtSlot as Slot 
     
    1920 
    2021 
     22#: Update file item states 
     23AVAILABLE, CURRENT, OUTDATED, DEPRECATED = range(4) 
     24 
     25_icons_dir = os.path.join(environ.canvas_install_dir, "icons") 
     26 
     27 
     28def icon(name): 
     29    return QIcon(os.path.join(_icons_dir, name)) 
     30 
     31 
    2132class ItemProgressBar(QProgressBar): 
    2233    """Progress Bar with and `advance()` slot. 
    2334    """ 
    24     @pyqtSignature("advance()") 
     35    @Slot() 
    2536    def advance(self): 
     37        """ 
     38        Advance the progress bar by 1 
     39        """ 
    2640        self.setValue(self.value() + 1) 
    2741 
    2842 
    29 _icons_dir = os.path.join(environ.canvas_install_dir, "icons") 
    30  
    31  
    32 def icon(name): 
    33     return QIcon(os.path.join(_icons_dir, name)) 
     43class UpdateOptionButton(QToolButton): 
     44    def event(self, event): 
     45        if event.type() == QEvent.Wheel: 
     46            # QAbstractButton automatically accepts all mouse events (in 
     47            # event method) for disabled buttons. This can prevent scrolling 
     48            # in a scroll area when a disabled button scrolls under the 
     49            # mouse. 
     50            event.ignore() 
     51            return False 
     52        else: 
     53            return QToolButton.event(self, event) 
    3454 
    3555 
     
    3858    A Widget with download/update/remove options. 
    3959    """ 
    40     def __init__(self, updateCallback, removeCallback, state, *args): 
    41         QWidget.__init__(self, *args) 
    42         self.updateCallback = updateCallback 
    43         self.removeCallback = removeCallback 
     60    #: Install/update button was clicked 
     61    installClicked = Signal() 
     62    #: Remove button was clicked. 
     63    removeClicked = Signal() 
     64 
     65    def __init__(self, state=AVAILABLE, parent=None): 
     66        QWidget.__init__(self, parent) 
    4467        layout = QHBoxLayout() 
    4568        layout.setSpacing(1) 
    4669        layout.setContentsMargins(1, 1, 1, 1) 
    47         self.updateButton = QToolButton(self) 
    48         self.updateButton.setIcon(icon("update.png")) 
    49         self.updateButton.setToolTip("Download") 
    50  
    51         self.removeButton = QToolButton(self) 
     70        self.installButton = UpdateOptionButton(self) 
     71        self.installButton.setIcon(icon("update.png")) 
     72        self.installButton.setToolTip("Download") 
     73 
     74        self.removeButton = UpdateOptionButton(self) 
    5275        self.removeButton.setIcon(icon("delete.png")) 
    5376        self.removeButton.setToolTip("Remove from system") 
    5477 
    55         self.connect(self.updateButton, SIGNAL("released()"), 
    56                      self.updateCallback) 
    57         self.connect(self.removeButton, SIGNAL("released()"), 
    58                      self.removeCallback) 
    59  
    60         self.setMaximumHeight(30) 
    61         layout.addWidget(self.updateButton) 
     78        self.installButton.clicked.connect(self.installClicked) 
     79        self.removeButton.clicked.connect(self.removeClicked) 
     80 
     81        layout.addWidget(self.installButton) 
    6282        layout.addWidget(self.removeButton) 
    6383        self.setLayout(layout) 
    64         self.SetState(state) 
    65  
    66     def SetState(self, state): 
    67         self.state = state 
    68         if state == 0: 
    69             self.updateButton.setIcon(icon("update1.png")) 
    70             self.updateButton.setToolTip("Update") 
    71             self.updateButton.setEnabled(False) 
     84 
     85        self.setMaximumHeight(30) 
     86 
     87        self.state = -1 
     88        self.setState(state) 
     89 
     90    def setState(self, state): 
     91        """ 
     92        Set the current update state for the widget (AVAILABLE, 
     93        CURRENT, OUTDATED or DEPRECTED). 
     94 
     95        """ 
     96        if self.state != state: 
     97            self.state = state 
     98            self._update() 
     99 
     100    def _update(self): 
     101        if self.state == AVAILABLE: 
     102            self.installButton.setIcon(icon("update.png")) 
     103            self.installButton.setToolTip("Download") 
     104            self.installButton.setEnabled(True) 
     105            self.removeButton.setEnabled(False) 
     106        elif self.state == CURRENT: 
     107            self.installButton.setIcon(icon("update1.png")) 
     108            self.installButton.setToolTip("Update") 
     109            self.installButton.setEnabled(False) 
    72110            self.removeButton.setEnabled(True) 
    73         elif state == 1: 
    74             self.updateButton.setIcon(icon("update1.png")) 
    75             self.updateButton.setToolTip("Update") 
    76             self.updateButton.setEnabled(True) 
     111        elif self.state == OUTDATED: 
     112            self.installButton.setIcon(icon("update1.png")) 
     113            self.installButton.setToolTip("Update") 
     114            self.installButton.setEnabled(True) 
    77115            self.removeButton.setEnabled(True) 
    78         elif state == 2: 
    79             self.updateButton.setIcon(icon("update.png")) 
    80             self.updateButton.setToolTip("Download") 
    81             self.updateButton.setEnabled(True) 
    82             self.removeButton.setEnabled(False) 
    83         elif state == 3: 
    84             self.updateButton.setIcon(icon("update.png")) 
    85             self.updateButton.setToolTip("") 
    86             self.updateButton.setEnabled(False) 
     116        elif self.state == DEPRECATED: 
     117            self.installButton.setIcon(icon("update.png")) 
     118            self.installButton.setToolTip("") 
     119            self.installButton.setEnabled(False) 
    87120            self.removeButton.setEnabled(True) 
    88121        else: 
    89             raise ValueError("Invalid state %r" % state) 
     122            raise ValueError("Invalid state %r" % self._state) 
    90123 
    91124 
    92125class UpdateTreeWidgetItem(QTreeWidgetItem): 
    93     stateDict = {0: "up-to-date", 
    94                  1: "new version available", 
    95                  2: "not downloaded", 
    96                  3: "obsolete"} 
    97  
    98     def __init__(self, master, treeWidget, domain, filename, infoLocal, 
    99                  infoServer, *args): 
    100         dateServer = dateLocal = None 
    101         if infoServer: 
    102             dateServer = datetime.strptime( 
    103                 infoServer["datetime"].split(".")[0], "%Y-%m-%d %H:%M:%S" 
    104             ) 
    105         if infoLocal: 
    106             dateLocal = datetime.strptime( 
    107                 infoLocal["datetime"].split(".")[0], "%Y-%m-%d %H:%M:%S" 
    108             ) 
    109         if not infoLocal: 
    110             self.state = 2 
    111         elif not infoServer: 
    112             self.state = 3 
     126    """ 
     127    A QTreeWidgetItem for displaying an UpdateItem. 
     128 
     129    :param UpdateItem item: 
     130        The update item for display. 
     131 
     132    """ 
     133    STATE_STRINGS = {0: "not downloaded", 
     134                     1: "downloaded, current", 
     135                     2: "downloaded, needs update", 
     136                     3: "obsolete"} 
     137 
     138    #: A role for the state item data. 
     139    StateRole = OWGUI.OrangeUserRole.next() 
     140 
     141    # QTreeWidgetItem stores the DisplayRole and EditRole as the same role, 
     142    # so we can't use EditRole to store the actual item data, instead we use 
     143    # custom role. 
     144 
     145    #: A custom edit role for the item's data 
     146    EditRole2 = OWGUI.OrangeUserRole.next() 
     147 
     148    def __init__(self, item): 
     149        QTreeWidgetItem.__init__(self, type=QTreeWidgetItem.UserType) 
     150 
     151        self.item = None 
     152        self.setUpdateItem(item) 
     153 
     154    def setUpdateItem(self, item): 
     155        """ 
     156        Set the update item for display. 
     157 
     158        :param UpdateItem item: 
     159            The update item for display. 
     160 
     161        """ 
     162        self.item = item 
     163 
     164        self.setData(0, UpdateTreeWidgetItem.StateRole, item.state) 
     165 
     166        self.setData(1, Qt.DisplayRole, item.title) 
     167        self.setData(1, self.EditRole2, item.title) 
     168 
     169        self.setData(2, Qt.DisplayRole, sizeof_fmt(item.size)) 
     170        self.setData(2, self.EditRole2, item.size) 
     171 
     172        if item.latest is not None: 
     173            self.setData(3, Qt.DisplayRole, item.latest.date().isoformat()) 
     174            self.setData(3, self.EditRole2, item.latest) 
    113175        else: 
    114             self.state = 0 if dateLocal >= dateServer else 1 
    115  
    116         title = infoServer["title"] if infoServer else (infoLocal["title"]) 
    117         tags = infoServer["tags"] if infoServer else infoLocal["tags"] 
    118         specialTags = dict([tuple(tag.split(":")) 
    119                             for tag in tags 
    120                             if tag.startswith("#") and ":" in tag]) 
    121         tags = ", ".join(tag for tag in tags if not tag.startswith("#")) 
    122         self.size = infoServer["size"] if infoServer else infoLocal["size"] 
    123  
    124         size = sizeof_fmt(float(self.size)) 
    125         state = self.stateDict[self.state] 
    126         if self.state == 1: 
    127             state += dateServer.strftime(" (%Y, %b, %d)") 
    128  
    129         QTreeWidgetItem.__init__(self, treeWidget, ["", title, size]) 
    130         if dateServer is not None: 
    131             self.setData(3, Qt.DisplayRole, 
    132                          dateServer.date().isoformat()) 
    133  
    134         self.updateWidget = UpdateOptionsWidget( 
    135             self.StartDownload, self.Remove, self.state, treeWidget 
    136         ) 
    137  
    138         self.treeWidget().setItemWidget(self, 0, self.updateWidget) 
    139         self.updateWidget.show() 
    140         self.master = master 
    141         self.title = title 
    142         self.tags = tags.split(", ") 
    143         self.specialTags = specialTags 
    144         self.domain = domain 
    145         self.filename = filename 
    146         self.task = None 
    147         self.UpdateToolTip() 
    148  
    149     def UpdateToolTip(self): 
    150         state = {0: "downloaded, current", 
    151                  1: "downloaded, needs update", 
    152                  2: "not downloaded", 
    153                  3: "obsolete"} 
    154         tooltip = "State: %s\nTags: %s" % (state[self.state], 
    155                                            ", ".join(self.tags)) 
    156  
    157         if self.state != 2: 
     176            self.setData(3, Qt.DisplayRole, "N/A") 
     177            self.setData(3, self.EditRole2, datetime.datetime()) 
     178 
     179        self._updateToolTip() 
     180 
     181    def _updateToolTip(self): 
     182        state_str = self.STATE_STRINGS[self.item.state] 
     183        tooltip = ("State: %s\nTags: %s" % 
     184                   (state_str, 
     185                    ", ".join(tag for tag in self.item.tags 
     186                              if not tag.startswith("#")))) 
     187 
     188        if self.item.state in [CURRENT, OUTDATED, DEPRECATED]: 
    158189            tooltip += ("\nFile: %s" % 
    159                         serverfiles.localpath(self.domain, self.filename)) 
    160         for i in range(1, 5): 
     190                        serverfiles.localpath(self.item.domain, 
     191                                              self.item.filename)) 
     192        for i in range(1, 4): 
    161193            self.setToolTip(i, tooltip) 
    162194 
    163     def StartDownload(self): 
    164         self.updateWidget.removeButton.setEnabled(False) 
    165         self.updateWidget.updateButton.setEnabled(False) 
    166         self.master.SubmitDownloadTask(self.domain, self.filename) 
    167  
    168     def Remove(self): 
    169         self.master.SubmitRemoveTask(self.domain, self.filename) 
    170  
    171     def __contains__(self, item): 
    172         return any(item.lower() in tag.lower() 
    173                    for tag in self.tags + [self.title]) 
    174  
    175195    def __lt__(self, other): 
    176         return getattr(self, "title", "") < getattr(other, "title", "") 
    177  
    178  
    179 class UpdateItemDelegate(QItemDelegate): 
     196        widget = self.treeWidget() 
     197        column = widget.sortColumn() 
     198        if column == 0: 
     199            role = UpdateTreeWidgetItem.StateRole 
     200        else: 
     201            role = self.EditRole2 
     202 
     203        left = self.data(column, role).toPyObject() 
     204        right = other.data(column, role).toPyObject() 
     205        return left < right 
     206 
     207 
     208class UpdateOptionsItemDelegate(QStyledItemDelegate): 
     209    """ 
     210    An item delegate for the updates tree widget. 
     211 
     212    .. note: Must be a child of a QTreeWidget. 
     213 
     214    """ 
    180215    def sizeHint(self, option, index): 
    181         size = QItemDelegate.sizeHint(self, option, index) 
     216        size = QStyledItemDelegate.sizeHint(self, option, index) 
    182217        parent = self.parent() 
    183218        item = parent.itemFromIndex(index) 
     
    188223 
    189224 
     225UpdateItem = namedtuple( 
     226    "UpdateItem", 
     227    ["domain", 
     228     "filename", 
     229     "state",  # Item state flag 
     230     "title",  # Item title (on server is available else local) 
     231     "size",  # Item size in bytes (on server if available else local) 
     232     "latest",  # Latest item date (on server), can be None 
     233     "local",  # Local item date, can be None 
     234     "tags",  # Item tags (on server if available else local) 
     235     "info_local", 
     236     "info_server"] 
     237) 
     238 
     239ItemInfo = namedtuple( 
     240    "ItemInfo", 
     241    ["domain", 
     242     "filename", 
     243     "title", 
     244     "time",  # datetime.datetime 
     245     "size",  # size in bytes 
     246     "tags"] 
     247) 
     248 
     249 
     250def UpdateItem_match(item, string): 
     251    """ 
     252    Return `True` if the `UpdateItem` item contains a string in tags 
     253    or in the title. 
     254 
     255    """ 
     256    string = string.lower() 
     257    return any(string.lower() in tag.lower() 
     258               for tag in item.tags + [item.title]) 
     259 
     260 
     261def item_state(info_local, info_server): 
     262    """ 
     263    Return the item state (AVAILABLE, ...) based on it's local and server side 
     264    `ItemInfo` instances. 
     265 
     266    """ 
     267    if info_server is None: 
     268        return DEPRECATED 
     269 
     270    if info_local is None: 
     271        return AVAILABLE 
     272 
     273    if info_local.time < info_server.time: 
     274        return OUTDATED 
     275    else: 
     276        return CURRENT 
     277 
     278 
     279DATE_FMT_1 = "%Y-%m-%d %H:%M:%S.%f" 
     280DATE_FMT_2 = "%Y-%m-%d %H:%M:%S" 
     281 
     282 
     283def info_dict_to_item_info(domain, filename, item_dict): 
     284    """ 
     285    Return an `ItemInfo` instance based on `item_dict` as returned by 
     286    ``serverfiles.info(domain, filename)`` 
     287 
     288    """ 
     289    time = item_dict["datetime"] 
     290    try: 
     291        time = datetime.strptime(time, DATE_FMT_1) 
     292    except ValueError: 
     293        time = datetime.strptime(time, DATE_FMT_2) 
     294 
     295    title = item_dict["title"] 
     296    if not title: 
     297        title = filename 
     298 
     299    size = int(item_dict["size"]) 
     300    tags = item_dict["tags"] 
     301    return ItemInfo(domain, filename, title, time, size, tags) 
     302 
     303 
     304def update_item_from_info(domain, filename, info_server, info_local): 
     305    """ 
     306    Return a `UpdateItem` instance for `domain`, `fileanme` based on 
     307    the local and server side `ItemInfo` instances `info_server` and 
     308    `info_local`. 
     309 
     310    """ 
     311    latest, local, title, tags, size = None, None, None, None, None 
     312 
     313    if info_server is not None: 
     314        info_server = info_dict_to_item_info(domain, filename, info_server) 
     315        latest = info_server.time 
     316        tags = info_server.tags 
     317        title = info_server.title 
     318        size = info_server.size 
     319 
     320    if info_local is not None: 
     321        info_local = info_dict_to_item_info(domain, filename, info_local) 
     322        local = info_local.time 
     323 
     324        if info_server is None: 
     325            tags = info_local.tags 
     326            title = info_local.title 
     327            size = info_local.size 
     328 
     329    state = item_state(info_local, info_server) 
     330 
     331    return UpdateItem(domain, filename, state, title, size, latest, local, 
     332                      tags, info_server, info_local) 
     333 
     334 
     335def join_info_list(domain, files_local, files_server): 
     336    filenames = set(files_local.keys()).union(files_server.keys()) 
     337    for filename in sorted(filenames): 
     338        info_server = files_server.get(filename, None) 
     339        info_local = files_local.get(filename, None) 
     340        yield update_item_from_info(domain, filename, info_server, info_local) 
     341 
     342 
     343def join_info_dict(local, server): 
     344    domains = set(local.keys()).union(server.keys()) 
     345    for domain in sorted(domains): 
     346        files_local = local.get(domain, {}) 
     347        files_server = server.get(domain, {}) 
     348 
     349        for item in join_info_list(domain, files_local, files_server): 
     350            yield item 
     351 
     352 
     353def special_tags(item): 
     354    """ 
     355    Return a dictionary of special tags in an UpdateItem instance (special 
     356    tags are the ones starting with #). 
     357 
     358    """ 
     359    return dict([tuple(tag.split(":")) for tag in item.tags 
     360                 if tag.startswith("#") and ":" in tag]) 
     361 
     362 
    190363def retrieveFilesList(serverFiles, domains=None, advance=lambda: None): 
    191364    """ 
     
    204377                 searchString="", showAll=True, domains=None, 
    205378                 accessCode=""): 
    206         OWWidget.__init__(self, parent, signalManager, name) 
     379        OWWidget.__init__(self, parent, signalManager, name, wantMainArea=False) 
    207380        self.searchString = searchString 
    208381        self.accessCode = accessCode 
     
    210383        self.domains = domains 
    211384        self.serverFiles = serverfiles.ServerFiles() 
    212         box = OWGUI.widgetBox(self.mainArea, orientation="horizontal") 
     385 
     386        box = OWGUI.widgetBox(self.controlArea, orientation="horizontal") 
    213387 
    214388        self.lineEditFilter = \ 
     
    221395                                 callback=self.SearchUpdate) 
    222396 
    223         box = OWGUI.widgetBox(self.mainArea, "Files") 
     397        box = OWGUI.widgetBox(self.controlArea, "Files") 
    224398        self.filesView = QTreeWidget(self) 
    225399        self.filesView.setHeaderLabels(["Options", "Title", "Size", 
    226400                                        "Last Updated"]) 
    227401        self.filesView.setRootIsDecorated(False) 
     402        self.filesView.setUniformRowHeights(True) 
    228403        self.filesView.setSelectionMode(QAbstractItemView.NoSelection) 
    229404        self.filesView.setSortingEnabled(True) 
    230         self.filesView.setItemDelegate(UpdateItemDelegate(self.filesView)) 
    231         self.connect(self.filesView.model(), 
    232                      SIGNAL("layoutChanged()"), 
    233                      self.SearchUpdate) 
     405        self.filesView.sortItems(1, Qt.AscendingOrder) 
     406        self.filesView.setItemDelegateForColumn( 
     407            0, UpdateOptionsItemDelegate(self.filesView)) 
     408 
     409        QObject.connect(self.filesView.model(), 
     410                        SIGNAL("layoutChanged()"), 
     411                        self.SearchUpdate) 
    234412        box.layout().addWidget(self.filesView) 
    235413 
    236         box = OWGUI.widgetBox(self.mainArea, orientation="horizontal") 
     414        box = OWGUI.widgetBox(self.controlArea, orientation="horizontal") 
    237415        OWGUI.button(box, self, "Update all local files", 
    238416                     callback=self.UpdateAll, 
     
    250428                                        callback=self.RetrieveFilesList) 
    251429        self.retryButton.hide() 
    252         box = OWGUI.widgetBox(self.mainArea, orientation="horizontal") 
     430        box = OWGUI.widgetBox(self.controlArea, orientation="horizontal") 
    253431        OWGUI.rubber(box) 
    254432        if wantCloseButton: 
     
    260438        self.infoLabel.setAlignment(Qt.AlignCenter) 
    261439 
    262         self.mainArea.layout().addWidget(self.infoLabel) 
     440        self.controlArea.layout().addWidget(self.infoLabel) 
    263441        self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) 
    264442 
    265443        self.updateItems = [] 
    266         self.allTags = [] 
    267444 
    268445        self.resize(800, 600) 
     
    297474 
    298475    def SetFilesList(self, serverInfo): 
     476        """ 
     477        Set the files to show. 
     478        """ 
    299479        self.setEnabled(True) 
    300         domains = serverInfo.keys() or serverfiles.listdomains() 
    301         localInfo = dict([(dom, serverfiles.allinfo(dom)) 
    302                           for dom in domains]) 
    303         items = [] 
    304  
    305         self.allTags = set() 
    306         allTitles = set() 
     480 
     481        domains = serverInfo.keys() 
     482        if not domains: 
     483            if self.domains: 
     484                domains = self.domains 
     485            else: 
     486                domains = serverfiles.listdomains() 
     487 
     488        localInfo = dict([(dom, serverfiles.allinfo(dom)) for dom in domains]) 
     489 
     490        all_tags = set() 
     491 
     492        self.filesView.clear() 
    307493        self.updateItems = [] 
    308494 
    309         for domain in set(domains) - set(["test", "demo"]): 
    310             local = localInfo.get(domain, {}) 
    311             server = serverInfo.get(domain, {}) 
    312             files = sorted(set(server.keys() + local.keys())) 
    313             for filename in files: 
    314                 infoServer = server.get(filename, None) 
    315                 infoLocal = local.get(filename, None) 
    316  
    317                 items.append((self.filesView, domain, filename, infoLocal, 
    318                               infoServer)) 
    319  
    320                 displayInfo = infoServer if infoServer else infoLocal 
    321                 self.allTags.update(displayInfo["tags"]) 
    322                 allTitles.update(displayInfo["title"].split()) 
    323  
    324         for item in items: 
    325             self.updateItems.append(UpdateTreeWidgetItem(self, *item)) 
     495        for item in join_info_dict(localInfo, serverInfo): 
     496            tree_item = UpdateTreeWidgetItem(item) 
     497            options_widget = UpdateOptionsWidget(item.state) 
     498            options_widget.item = item 
     499 
     500            # Connect the actions to the appropriate methods 
     501            options_widget.installClicked.connect( 
     502                partial(self.SubmitDownloadTask, item.domain, item.filename) 
     503            ) 
     504            options_widget.removeClicked.connect( 
     505                partial(self.SubmitRemoveTask, item.domain, item.filename) 
     506            ) 
     507 
     508            self.updateItems.append((item, tree_item, options_widget)) 
     509            all_tags.update(item.tags) 
     510 
     511        self.filesView.addTopLevelItems( 
     512            [tree_item for _, tree_item, _ in self.updateItems] 
     513        ) 
     514 
     515        for item, tree_item, options_widget in self.updateItems: 
     516            self.filesView.setItemWidget(tree_item, 0, options_widget) 
     517 
    326518        self.progress.advance() 
    327519 
     
    331523            self.filesView.setColumnWidth(column, width) 
    332524 
    333         self.lineEditFilter.setItems([hint for hint in sorted(self.allTags) 
     525        self.lineEditFilter.setItems([hint for hint in sorted(all_tags) 
    334526                                      if not hint.startswith("#")]) 
    335527        self.SearchUpdate() 
     
    350542 
    351543    def UpdateInfoLabel(self): 
    352         local = [item for item in self.updateItems if item.state != 2] 
    353         onServer = [item for item in self.updateItems] 
    354         size = sum(float(item.specialTags.get("#uncompressed", item.size)) 
     544        local = [item for item, _, _ in self.updateItems 
     545                 if item.state != AVAILABLE] 
     546        onServer = [item for item, _, _ in self.updateItems] 
     547 
     548        size = sum(float(special_tags(item).get("#uncompressed", item.size)) 
    355549                   for item in local) 
    356         sizeOnServer = sum(float(item.size) for item in self.updateItems) 
     550 
     551        sizeOnServer = sum(float(item.size) for item, _, _ in self.updateItems) 
    357552 
    358553        if self.showAll: 
     
    369564 
    370565    def UpdateAll(self): 
    371         for item in self.updateItems: 
    372             if item.state == 1: 
    373                 item.StartDownload() 
     566        for item, _, _ in self.updateItems: 
     567            if item.state == OUTDATED: 
     568                self.SubmitDownloadTask(item.domain, item.filename) 
    374569 
    375570    def DownloadFiltered(self): 
    376         for item in self.updateItems: 
    377             if not item.isHidden() and item.state != 0: 
    378                 item.StartDownload() 
     571        # TODO: submit items in the order shown. 
     572        for item, tree_item, _ in self.updateItems: 
     573            if not tree_item.isHidden() and item.state in \ 
     574                    [AVAILABLE, OUTDATED]: 
     575                self.SubmitDownloadTask(item.domain, item.filename) 
    379576 
    380577    def SearchUpdate(self, searchString=None): 
    381578        strings = unicode(self.lineEditFilter.text()).split() 
    382         tags = set() 
    383         for item in self.updateItems: 
    384             hide = not all(str(string) in item for string in strings) 
    385             item.setHidden(hide) 
    386             if not hide: 
    387                 tags.update(item.tags) 
     579        for item, tree_item, _ in self.updateItems: 
     580            hide = not all(UpdateItem_match(item, string) 
     581                           for string in strings) 
     582            tree_item.setHidden(hide) 
    388583 
    389584    def SubmitDownloadTask(self, domain, filename): 
     
    391586        Submit the (domain, filename) to be downloaded/updated. 
    392587        """ 
    393         item = self._item(domain, filename) 
     588        index = self.updateItemIndex(domain, filename) 
     589        _, tree_item, opt_widget = self.updateItems[index] 
    394590 
    395591        if self.accessCode: 
     
    400596        task = DownloadTask(domain, filename, sf) 
    401597 
    402         future = self.executor.submit(task) 
    403  
    404 #        watcher = FutureWatcher(future, parent=self) 
    405 #        watcher.finished.connect(progress.finish) 
     598        self.executor.submit(task) 
    406599 
    407600        self.progress.adjustRange(0, 100) 
     
    417610        task.exception.connect(self.onDownloadError, Qt.QueuedConnection) 
    418611 
    419         self.filesView.setItemWidget(item, 2, pb) 
     612        self.filesView.setItemWidget(tree_item, 2, pb) 
    420613 
    421614        # Clear the text so it does not show behind the progress bar. 
    422         item.setData(2, Qt.DisplayRole, QVariant("")) 
     615        tree_item.setData(2, Qt.DisplayRole, "") 
    423616        pb.show() 
    424617 
     618        # Disable the options widget 
     619        opt_widget.setEnabled(False) 
    425620        self._tasks.append(task) 
    426 #        self._futures.append((future, watcher)) 
    427621 
    428622    def EndDownloadTask(self, task): 
    429623        future = task.future() 
    430         item = self._item(task.domain, task.filename) 
    431  
    432         self.filesView.removeItemWidget(item, 2) 
     624        index = self.updateItemIndex(task.domain, task.filename) 
     625        item, tree_item, opt_widget = self.updateItems[index] 
     626 
     627        self.filesView.removeItemWidget(tree_item, 2) 
     628        opt_widget.setEnabled(True) 
    433629 
    434630        if future.cancelled(): 
    435631            # Restore the previous state 
    436             item.updateWidget.SetState(item.state) 
    437             item.setData(2, Qt.DisplayRole, 
    438                          QVariant(sizeof_fmt(float(item.size)))) 
     632            tree_item.setUpdateItem(item) 
     633            opt_widget.setState(item.state) 
    439634 
    440635        elif future.exception(): 
    441             item.updateWidget.SetState(1) 
    442             item.setData(2, Qt.DisplayRole, 
     636            tree_item.setUpdateItem(item) 
     637            opt_widget.setState(item.state) 
     638 
     639            # Show the exception string in the size column. 
     640            tree_item.setData(2, Qt.DisplayRole, 
    443641                         QVariant("Error occurred while downloading:" + 
    444642                                  str(future.exception()))) 
    445 #            item.setErrorText(str(exception)) 
    446 #            item.setState(UpdateTreeWidgetItem.Error) 
     643 
    447644        else: 
    448             item.state = 0 
    449             item.updateWidget.SetState(item.state) 
    450             item.setData(2, Qt.DisplayRole, 
    451                          QVariant(sizeof_fmt(float(item.size)))) 
    452             item.UpdateToolTip() 
     645            # get the new updated info dict and replace the the old item 
     646            info = serverfiles.info(item.domain, item.filename) 
     647            new_item = update_item_from_info(item.domain, item.filename, 
     648                                             info, info) 
     649 
     650            self.updateItems[index] = (new_item, tree_item, opt_widget) 
     651 
     652            tree_item.setUpdateItem(new_item) 
     653            opt_widget.setState(new_item.state) 
     654 
    453655            self.UpdateInfoLabel() 
    454  
    455 #            item.setState(UpdateTreeWidgetItem.Updated) 
    456 #            item.setInfo(serverfiles.info(task.domain, task.filename)) 
    457656 
    458657    def SubmitRemoveTask(self, domain, filename): 
    459658        serverfiles.remove(domain, filename) 
    460  
    461         item = self._item(domain, filename) 
    462         item.state = 2 
    463         item.updateWidget.SetState(item.state) 
     659        index = self.updateItemIndex(domain, filename) 
     660        item, tree_item, opt_widget = self.updateItems[index] 
     661 
     662        if item.info_server: 
     663            new_item = item._replace(state=AVAILABLE, local=None, 
     664                                      info_local=None) 
     665        else: 
     666            new_item = item._replace(local=None, info_local=None) 
     667            # Disable the options widget. No more actions can be performed 
     668            # for the item. 
     669            opt_widget.setEnabled(False) 
     670 
     671        tree_item.setUpdateItem(new_item) 
     672        opt_widget.setState(new_item.state) 
     673        self.updateItems[index] = (new_item, tree_item, opt_widget) 
    464674 
    465675        self.UpdateInfoLabel() 
    466         item.UpdateToolTip() 
    467676 
    468677    def Cancel(self): 
     
    470679        Cancel all pending update/download tasks (that have not yet started). 
    471680        """ 
    472         print "Cancel" 
    473681        for task in self._tasks: 
    474             print task, task.future().cancel() 
     682            task.future().cancel() 
    475683 
    476684    def onDeleteWidget(self): 
     
    491699            self.progress.setRange(0, 0) 
    492700 
    493         print "Download finished" 
    494  
    495701    def onDownloadError(self, exc_info): 
    496702        sys.excepthook(*exc_info) 
    497703 
    498     def _item(self, domain, filename): 
    499         return [item for item in self.updateItems 
    500                 if item.domain == domain and item.filename == filename].pop() 
     704    def updateItemIndex(self, domain, filename): 
     705        for i, (item, _, _) in enumerate(self.updateItems): 
     706            if item.domain == domain and item.filename == filename: 
     707                return i 
     708        raise ValueError("%r, %r not in update list" % (domain, filename)) 
    501709 
    502710    def _updateProgress(self, *args): 
     
    602810 
    603811    def run(self): 
    604         print "Download task", QThread.currentThread() 
    605812        try: 
    606813            serverfiles.download(self.domain, self.filename, self.serverfiles, 
     
    609816            self.exception.emit(sys.exc_info()) 
    610817 
    611         print "Finished" 
    612  
    613818 
    614819if __name__ == "__main__": 
Note: See TracChangeset for help on using the changeset viewer.