Exemple #1
0
    def on_tribler_statistics(self, data):
        if not data:
            return
        data = data["tribler_statistics"]
        self.window().general_tree_widget.clear()
        self.create_and_add_widget_item("Tribler version", self.tribler_version, self.window().general_tree_widget)
        self.create_and_add_widget_item(
            "Python version", sys.version.replace('\n', ''), self.window().general_tree_widget  # to fit in one line
        )
        self.create_and_add_widget_item("Libtorrent version", libtorrent.version, self.window().general_tree_widget)
        self.create_and_add_widget_item("", "", self.window().general_tree_widget)

        self.create_and_add_widget_item("Number of channels", data["num_channels"], self.window().general_tree_widget)
        self.create_and_add_widget_item(
            "Database size", format_size(data["db_size"]), self.window().general_tree_widget
        )
        self.create_and_add_widget_item(
            "Number of known torrents", data["num_torrents"], self.window().general_tree_widget
        )
        self.create_and_add_widget_item("", "", self.window().general_tree_widget)

        disk_usage = psutil.disk_usage('/')
        self.create_and_add_widget_item(
            "Total disk space", format_size(disk_usage.total), self.window().general_tree_widget
        )
        self.create_and_add_widget_item(
            "Used disk space", format_size(disk_usage.used), self.window().general_tree_widget
        )
        self.create_and_add_widget_item(
            "Free disk space", format_size(disk_usage.free), self.window().general_tree_widget
        )
 def update_torrent_size_label(self):
     total_files_size = self.dialog_widget.files_list_view.total_files_size
     selected_files_size = self.dialog_widget.files_list_view.selected_files_size
     if total_files_size == selected_files_size:
         label_text = tr("Torrent size: ") + format_size(total_files_size)
     else:
         label_text = (tr("Selected: ") + format_size(selected_files_size) +
                       " / " + tr("Total: ") +
                       format_size(total_files_size))
     self.dialog_widget.loading_files_label.setStyleSheet("color:#ffffff;")
     self.dialog_widget.loading_files_label.setText(label_text)
Exemple #3
0
    def should_cleanup_old_versions(self) -> List[TriblerVersion]:
        if self.version_history.last_run_version == self.version_history.code_version:
            return []

        disposable_versions = self.version_history.get_disposable_versions(skip_versions=2)
        if not disposable_versions:
            return []

        storage_info = ""
        claimable_storage = 0
        for version in disposable_versions:
            state_size = version.calc_state_size()
            claimable_storage += state_size
            storage_info += f"{version.version_str} \t {format_size(state_size)}\n"

        # Show a question to the user asking if the user wants to remove the old data.
        title = "Delete state directories for old versions?"
        message_body = tr(
            "Press 'Yes' to remove state directories for older versions of Tribler "
            "and reclaim %s of storage space. "
            "Tribler used those directories during upgrades from previous versions. "
            "Now those directories can be safely deleted. \n\n"
            "If unsure, press 'No'. "
            "You will be able to remove those directories from the Settings->Data page later."
        ) % format_size(claimable_storage)

        user_choice = self._show_question_box(title, message_body, storage_info, default_button=QMessageBox.Yes)
        if user_choice == QMessageBox.Yes:
            return disposable_versions
        return []
Exemple #4
0
 def update_item(self):
     self.setText(0, self.file_info["name"])
     self.setText(1, format_size(float(self.file_info["size"])))
     self.setText(
         2, '{percent:.1%}'.format(percent=self.file_info["progress"]))
     self.setText(3, "yes" if self.file_info["included"] else "no")
     self.setData(0, Qt.UserRole, self.file_info)
 def fill_directory_sizes(self):
     if self.file_size is None:
         self.file_size = 0
         for child in self.children:
             self.file_size += child.fill_directory_sizes()
     self.setText(SIZE_COL, format_size(float(self.file_size)))
     return self.file_size
Exemple #6
0
    def update_item(self):
        self.setText(0, self.download_info["name"])

        if self.download_info["size"] == 0 and self.get_raw_download_status() == DLSTATUS_METADATA:
            self.setText(1, "unknown")
        else:
            self.setText(1, format_size(float(self.download_info["size"])))

        try:
            self.progress_slider.setValue(int(self.download_info["progress"] * 100))
        except RuntimeError:
            self._logger.error("The underlying GUI widget has already been removed.")

        if self.download_info["vod_mode"]:
            self.setText(3, "Streaming")
        else:
            self.setText(3, DLSTATUS_STRINGS[dlstatus_strings.index(self.download_info["status"])])
        self.setText(4, "%s (%s)" % (self.download_info["num_connected_seeds"], self.download_info["num_seeds"]))
        self.setText(5, "%s (%s)" % (self.download_info["num_connected_peers"], self.download_info["num_peers"]))
        self.setText(6, format_speed(self.download_info["speed_down"]))
        self.setText(7, format_speed(self.download_info["speed_up"]))
        self.setText(8, "%.3f" % float(self.download_info["ratio"]))
        self.setText(9, "yes" if self.download_info["anon_download"] else "no")
        self.setText(10, str(self.download_info["hops"]) if self.download_info["anon_download"] else "-")
        self.setText(12, datetime.fromtimestamp(int(self.download_info["time_added"])).strftime('%Y-%m-%d %H:%M'))

        eta_text = "-"
        if self.get_raw_download_status() == DLSTATUS_DOWNLOADING:
            eta_text = duration_to_string(self.download_info["eta"])
        self.setText(11, eta_text)
Exemple #7
0
def define_columns():
    d = ColumnDefinition
    # fmt:off
    # pylint: disable=line-too-long
    columns_dict = {
        Column.ACTIONS:
        d('', "", width=60, sortable=False),
        Column.CATEGORY:
        d('category', "", width=30, tooltip_filter=lambda data: data),
        Column.NAME:
        d('name', tr("Name"), width=EXPANDING),
        Column.SIZE:
        d('size',
          tr("Size"),
          width=90,
          display_filter=lambda data: (format_size(float(data))
                                       if data != "" else "")),
        Column.HEALTH:
        d(
            'health',
            tr("Health"),
            width=100,
            tooltip_filter=lambda data: f"{data}" +
            ('' if data == HEALTH_CHECKING else '\n(Click to recheck)'),
        ),
        Column.UPDATED:
        d(
            'updated',
            tr("Updated"),
            width=120,
            display_filter=lambda timestamp: pretty_date(timestamp)
            if timestamp and timestamp > BITTORRENT_BIRTHDAY else 'N/A',
        ),
        Column.VOTES:
        d(
            'votes',
            tr("Popularity"),
            width=120,
            display_filter=format_votes,
            tooltip_filter=lambda data: get_votes_rating_description(data)
            if data is not None else None,
        ),
        Column.STATUS:
        d('status', "", sortable=False),
        Column.STATE:
        d('state',
          "",
          width=80,
          tooltip_filter=lambda data: data,
          sortable=False),
        Column.TORRENTS:
        d('torrents', tr("Torrents"), width=90),
        Column.SUBSCRIBED:
        d('subscribed', tr("Subscribed"), width=90),
    }
    # pylint: enable=line-too-long
    # fmt:on
    return columns_dict
    def on_received_metainfo(self, response):
        if not response or not self or self.closed:
            return

        if 'error' in response:
            if response['error'] == 'metainfo error':
                # If it failed to load metainfo for max number of times, show an error message in red.
                if self.metainfo_retries > METAINFO_MAX_RETRIES:
                    self.dialog_widget.loading_files_label.setStyleSheet(
                        "color:#ff0000;")
                    self.dialog_widget.loading_files_label.setText(
                        "Failed to load files. Click to retry again.")
                    return
                self.perform_files_request()

            elif 'code' in response['error'] and response['error'][
                    'code'] == 'IOError':
                self.dialog_widget.loading_files_label.setText(
                    "Unable to read torrent file data")
            else:
                self.dialog_widget.loading_files_label.setText(
                    f"Error: {response['error']}")
            return

        metainfo = json.loads(unhexlify(response['metainfo']))
        if 'files' in metainfo['info']:  # Multi-file torrent
            files = metainfo['info']['files']
        else:
            files = [{
                'path': [metainfo['info']['name']],
                'length': metainfo['info']['length']
            }]

        # Show if the torrent already exists in the downloads
        if response.get('download_exists'):
            self.dialog_widget.existing_download_info_label.setText(
                "Note: this torrent already exists in the Downloads")
        else:
            self.dialog_widget.existing_download_info_label.setText("")

        self.dialog_widget.files_list_view.clear()
        for filename in files:
            item = DownloadFileTreeWidgetItem(
                self.dialog_widget.files_list_view)
            item.setText(0, '/'.join(filename['path']))
            item.setText(1, format_size(float(filename['length'])))
            item.setData(0, Qt.UserRole, filename)
            item.setCheckState(2, Qt.Checked)
            self.dialog_widget.files_list_view.addTopLevelItem(item)

        self.has_metainfo = True
        self.dialog_widget.loading_files_label.setHidden(True)
        self.dialog_widget.download_files_container.setHidden(False)
        self.dialog_widget.files_list_view.setHidden(False)
        self.dialog_widget.adjustSize()
        self.on_main_window_resize()

        self.received_metainfo.emit(metainfo)
Exemple #9
0
    def update_pages(self, new_download=False):
        if self.current_download is None:
            return

        if "files" not in self.current_download:
            self.current_download["files"] = []

        self.window().download_progress_bar.update_with_download(self.current_download)
        self.window().download_detail_name_label.setText(self.current_download['name'])

        if self.current_download["vod_mode"]:
            self.window().download_detail_status_label.setText('Streaming')
        else:
            status_string = DLSTATUS_STRINGS[dlstatus_strings.index(self.current_download["status"])]
            if dlstatus_strings.index(self.current_download["status"]) == DLSTATUS_STOPPED_ON_ERROR:
                status_string += f" (error: {self.current_download['error']})"
            self.window().download_detail_status_label.setText(status_string)

        self.window().download_detail_filesize_label.setText(
            tr("%(num_bytes)s in %(num_files)d files")
            % {
                'num_bytes': format_size(float(self.current_download["size"])),
                'num_files': len(self.current_download["files"]),
            }
        )
        self.window().download_detail_health_label.setText(
            tr("%d seeders, %d leechers") % (self.current_download["num_seeds"], self.current_download["num_peers"])
        )
        self.window().download_detail_infohash_label.setText(self.current_download['infohash'])
        self.window().download_detail_destination_label.setText(self.current_download["destination"])
        self.window().download_detail_ratio_label.setText(
            "%.3f, up: %s, down: %s"
            % (
                self.current_download["ratio"],
                format_size(self.current_download["total_up"]),
                format_size(self.current_download["total_down"]),
            )
        )
        self.window().download_detail_availability_label.setText(f"{self.current_download['availability']:.2f}")

        if force_update := (new_download or self.window().download_files_list.is_empty):
            # (re)populate the files list
            self.window().download_files_list.clear()
            files = convert_to_files_tree_format(self.current_download)
            self.window().download_files_list.fill_entries(files)
Exemple #10
0
    def update_status_bar(self, selected_node):
        if not selected_node:
            return

        peer_message = f"<b>User</b> {HTML_SPACE * 16}{selected_node.get('public_key', '')[:74]}..."
        self.window().tr_selected_node_pub_key.setHidden(False)
        self.window().tr_selected_node_pub_key.setText(peer_message)

        diff = selected_node.get('total_up', 0) - selected_node.get(
            'total_down', 0)
        color = COLOR_GREEN if diff > 0 else COLOR_RED if diff < 0 else COLOR_DEFAULT
        bandwidth_message = (
            "<b>Bandwidth</b> " + HTML_SPACE * 2 + " Given " + HTML_SPACE +
            html_label(format_size(selected_node.get('total_up', 0))) +
            " Taken " + HTML_SPACE +
            html_label(format_size(selected_node.get('total_down', 0))) +
            " Balance " + HTML_SPACE +
            html_label(format_size(diff), color=color))
        self.window().tr_selected_node_stats.setHidden(False)
        self.window().tr_selected_node_stats.setText(bandwidth_message)
Exemple #11
0
 def add_items_to_tree(self, tree, items, keys):
     tree.clear()
     for item in items:
         widget_item = QTreeWidgetItem(tree)
         for index, key in enumerate(keys):
             if key in ["bytes_up", "bytes_down"]:
                 value = format_size(item[key])
             elif key in ["creation_time", "last_lookup"]:
                 value = str(datetime.timedelta(seconds=int(time() - item[key]))) if item[key] > 0 else '-'
             else:
                 value = str(item[key])
             widget_item.setText(index, value)
         tree.addTopLevelItem(widget_item)
    def update_with_torrent(self, index, torrent_info):
        self.torrent_info = torrent_info
        self.index = index
        self.torrent_detail_name_label.setText(self.torrent_info["name"])
        if self.torrent_info["category"]:
            self.torrent_detail_category_label.setText(
                self.torrent_info["category"].lower())
        else:
            self.torrent_detail_category_label.setText("unknown")

        if self.torrent_info["size"] is None:
            self.torrent_detail_size_label.setText("Size: -")
        else:
            self.torrent_detail_size_label.setText(
                "%s" % format_size(float(self.torrent_info["size"])))

        self.update_health_label(torrent_info['num_seeders'],
                                 torrent_info['num_leechers'],
                                 torrent_info['last_tracker_check'])

        self.torrent_detail_infohash_label.setText(
            self.torrent_info["infohash"])

        self.setCurrentIndex(0)
        self.setTabEnabled(1, True)

        # If we do not have the health of this torrent, query it, but do it delayed.
        # When the user scrolls the list, we only want to trigger health checks on the line
        # that the user stopped on, so we do not generate excessive health checks.
        if self.is_health_checking:
            if self.rest_request1:
                self.rest_request1.cancel_request()
            if self.rest_request2:
                self.rest_request2.cancel_request()
            self.is_health_checking = False
        if torrent_info['last_tracker_check'] == 0:
            if not self.healthcheck_timer.isActive():
                self.on_check_health_clicked()
            self.healthcheck_timer.stop()
            self.healthcheck_timer.start(HEALTHCHECK_DELAY)
        self.update_health_label(torrent_info['num_seeders'],
                                 torrent_info['num_leechers'],
                                 torrent_info['last_tracker_check'])

        self.torrent_detail_trackers_list.clear()
    def fill_entries(self, files):
        if not files:
            return

        # Block the signals to prevent unnecessary recalculation of directory sizes
        self.blockSignals(True)
        self.clear()

        # ACHTUNG!
        # Workaround for QT eliding size text too aggressively, resulting in incorrect column size
        # The downside is no eliding for the names column
        self.setTextElideMode(Qt.ElideNone)
        self.header().setSectionResizeMode(QHeaderView.ResizeToContents)
        single_item_torrent = len(files) == 1

        # !!! ACHTUNG !!!
        # The styling must be applied right before or right after filling the table,
        # otherwise it won't work properly.
        self.setStyleSheet(TORRENT_FILES_TREE_STYLESHEET)

        self.total_files_size = 0
        items = {'': self}
        for file_index, file in enumerate(files):
            path = file['path']
            for i, obj_name in enumerate(path):
                parent_path = "/".join(path[:i])
                full_path = "/".join(path[:i + 1])
                if full_path in items:
                    continue

                is_file = i == len(path) - 1
                item = items[full_path] = DownloadFileTreeWidgetItem(
                    items[parent_path],
                    file_index=file_index if is_file else None,
                    file_progress=file.get('progress'),
                )
                item.setText(FILENAME_COL, obj_name)
                item.setData(FILENAME_COL, Qt.UserRole, obj_name)

                file_included = file.get('included', True)

                item.setCheckState(
                    CHECKBOX_COL,
                    Qt.Checked if file_included else Qt.Unchecked)

                if single_item_torrent:
                    item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable)

                if is_file:
                    # Add file size info for file entries
                    item.file_size = int(file['length'])
                    self.total_files_size += item.file_size
                    item.setText(SIZE_COL, format_size(float(file['length'])))
                else:
                    # Make folder checkboxes automatically affect subtree items
                    item.setFlags(item.flags() | Qt.ItemIsAutoTristate)

        for ind in range(self.topLevelItemCount()):
            self.topLevelItem(ind).fill_directory_sizes()

        # Automatically open the toplevel item
        if self.topLevelItemCount() == 1:
            item = self.topLevelItem(0)
            if item.childCount() > 0:
                self.expandItem(item)

        self.blockSignals(False)
        self.selected_files_size = sum(item.file_size
                                       for item in self.get_selected_items()
                                       if item.file_index is not None)
        self.selected_files_changed.emit()
Exemple #14
0
    def update_pages(self, new_download=False):
        if self.current_download is None:
            return

        if "files" not in self.current_download:
            self.current_download["files"] = []

        self.window().download_progress_bar.update_with_download(
            self.current_download)
        self.window().download_detail_name_label.setText(
            self.current_download['name'])

        if self.current_download["vod_mode"]:
            self.window().download_detail_status_label.setText('Streaming')
        else:
            status_string = DLSTATUS_STRINGS[dlstatus_strings.index(
                self.current_download["status"])]
            if dlstatus_strings.index(self.current_download["status"]
                                      ) == DLSTATUS_STOPPED_ON_ERROR:
                status_string += f" (error: {self.current_download['error']})"
            self.window().download_detail_status_label.setText(status_string)

        self.window().download_detail_filesize_label.setText(
            tr("%(num_bytes)s in %(num_files)d files") % {
                'num_bytes': format_size(float(self.current_download["size"])),
                'num_files': len(self.current_download["files"]),
            })
        self.window().download_detail_health_label.setText(
            tr("%d seeders, %d leechers") %
            (self.current_download["num_seeds"],
             self.current_download["num_peers"]))
        self.window().download_detail_infohash_label.setText(
            self.current_download['infohash'])
        self.window().download_detail_destination_label.setText(
            self.current_download["destination"])
        self.window().download_detail_ratio_label.setText(
            "%.3f, up: %s, down: %s" % (
                self.current_download["ratio"],
                format_size(self.current_download["total_up"]),
                format_size(self.current_download["total_down"]),
            ))
        self.window().download_detail_availability_label.setText(
            f"{self.current_download['availability']:.2f}")

        if new_download or len(self.current_download["files"]) != len(
                self.files_widgets.keys()):

            # (re)populate the files list
            self.window().download_files_list.clear()
            self.files_widgets = {}
            for dfile in self.current_download["files"]:
                item = DownloadFileWidgetItem(
                    self.window().download_files_list, dfile)
                DownloadsDetailsTabWidget.update_file_row(item, dfile)
                self.files_widgets[dfile["name"]] = item

        else:  # No new download, just update data in the lists
            for dfile in self.current_download["files"]:
                DownloadsDetailsTabWidget.update_file_row(
                    self.files_widgets[dfile["name"]], dfile)

        # Populate the trackers list
        self.window().download_trackers_list.clear()
        for tracker in self.current_download["trackers"]:
            item = QTreeWidgetItem(self.window().download_trackers_list)
            DownloadsDetailsTabWidget.update_tracker_row(item, tracker)

        # Populate the peers list if the peer information is available
        self.window().download_peers_list.clear()
        if "peers" in self.current_download:
            for peer in self.current_download["peers"]:
                item = QTreeWidgetItem(self.window().download_peers_list)
                DownloadsDetailsTabWidget.update_peer_row(item, peer)
Exemple #15
0
class ChannelContentModel(RemoteTableModel):

    columns = [
        u'category', u'name', u'size', u'health', u'updated', ACTION_BUTTONS
    ]
    column_headers = [
        u'Category', u'Name', u'Size', u'Health', u'Updated', u''
    ]
    unsortable_columns = [u'status', u'state', ACTION_BUTTONS]
    column_flags = {
        u'subscribed': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        u'category': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        u'name': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        u'torrents': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        u'size': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        u'updated': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        u'health': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        u'votes': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        u'state': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        u'status': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        ACTION_BUTTONS: Qt.ItemIsEnabled | Qt.ItemIsSelectable,
    }

    column_width = {
        u'state': lambda _: 20,
        u'name': lambda table_width: table_width - 510,
        u'action_buttons': lambda _: 70,
    }

    column_tooltip_filters = {
        u'state': lambda data: data,
        u'votes': lambda data: "{0:.0%}".format(float(data)) if data else None,
    }

    column_display_filters = {
        u'size':
        lambda data: (format_size(float(data)) if data != '' else ''),
        u'votes':
        format_votes,
        u'state':
        lambda data: str(data)[:1] if data == u'Downloading' else "",
        u'updated':
        lambda timestamp: pretty_date(timestamp)
        if timestamp and timestamp > BITTORRENT_BIRTHDAY else 'N/A',
    }

    def __init__(
        self,
        channel_info=None,
        hide_xxx=None,
        exclude_deleted=None,
        subscribed_only=None,
        endpoint_url=None,
        text_filter='',
    ):
        RemoteTableModel.__init__(self, parent=None)
        self.column_position = {name: i for i, name in enumerate(self.columns)}

        self.data_items = []

        # Remote query (model) parameters
        self.hide_xxx = hide_xxx
        self.text_filter = text_filter
        self.subscribed_only = subscribed_only
        self.exclude_deleted = exclude_deleted
        self.type_filter = None
        self.category_filter = None

        # Current channel attributes. This is intentionally NOT copied, so local changes
        # can propagate to the origin, e.g. parent channel.
        self.channel_info = channel_info or {
            "name": "My channels",
            "status": 123
        }

        self.endpoint_url_override = endpoint_url

        # Load the initial batch of entries
        self.perform_query()

    @property
    def edit_enabled(self):
        return False

    @property
    def endpoint_url(self):
        return self.endpoint_url_override or "channels/%s/%i" % (
            self.channel_info["public_key"],
            self.channel_info["id"],
        )

    def headerData(self, num, orientation, role=None):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self.column_headers[num]
        if role == Qt.InitialSortOrderRole and num != self.column_position.get(
                'name'):
            return Qt.DescendingOrder

    def rowCount(self, parent=QModelIndex()):
        return len(self.data_items)

    def columnCount(self, parent=QModelIndex()):
        return len(self.columns)

    def flags(self, index):
        return self.column_flags[self.columns[index.column()]]

    def filter_item_txt(self, txt_filter, index, show_default=True):
        # FIXME: dumb workaround for some mysterious race condition
        try:
            item = self.data_items[index.row()]
        except IndexError:
            return ""

        column = self.columns[index.column()]
        data = item.get(column, u'')

        # Print number of torrents in the channel for channel rows in the "size" column
        if (column == "size" and "torrents" not in self.columns
                and "torrents" in item
                and item["type"] in (CHANNEL_TORRENT, COLLECTION_NODE)):
            return item["torrents"]

        if column in txt_filter:
            display_txt = txt_filter.get(column, str(data))(data)
        elif show_default:
            display_txt = data
        else:
            display_txt = None
        return display_txt

    def data(self, index, role):
        if role == Qt.DisplayRole or role == Qt.EditRole:
            return self.filter_item_txt(self.column_display_filters, index)
        elif role == Qt.ToolTipRole:
            return self.filter_item_txt(self.column_tooltip_filters,
                                        index,
                                        show_default=False)
        elif role == Qt.TextAlignmentRole:
            if index.column() == self.column_position.get(u'votes', -1):
                return Qt.AlignLeft | Qt.AlignVCenter
        return None

    def reset(self):
        self.item_uid_map.clear()
        super(ChannelContentModel, self).reset()

    def update_node_info(self, update_dict):
        """
        This method updates/inserts rows based on updated_dict. It should be typically invoked
        by a signal from Events endpoint. One special case it when the channel_info of the model
        itself is updated. In that case, info_changed signal is emitted, so the controller/widget knows
        it is time to update the labels.
        """
        # TODO: better mechanism for identifying channel entries for pushing updates

        if (self.channel_info.get("public_key") ==
                update_dict.get("public_key") is not None
                and self.channel_info.get("id") == update_dict.get("id") is
                not None):
            self.channel_info.update(**update_dict)
            self.info_changed.emit([])

        row = self.item_uid_map.get(get_item_uid(update_dict))
        if row in self.data_items:
            self.data_items[row].update(**update_dict)
            self.dataChanged.emit(self.index(row, 0),
                                  self.index(row, len(self.columns)), [])

    def perform_query(self, **kwargs):
        """
        Fetch search results.
        """

        if self.type_filter is not None:
            kwargs.update({"metadata_type": self.type_filter})
        if self.subscribed_only is not None:
            kwargs.update({"subscribed": self.subscribed_only})
        if self.exclude_deleted is not None:
            kwargs.update({"exclude_deleted": self.exclude_deleted})
        if self.category_filter is not None:
            if self.category_filter == "Channels":
                kwargs.update({'metadata_type': 'channel'})
            else:
                kwargs.update({"category": self.category_filter})

        if "total" not in self.channel_info:
            # Only include total for the first query to the endpoint
            kwargs.update({"include_total": 1})

        super(ChannelContentModel, self).perform_query(**kwargs)

    def setData(self, index, new_value, role=None):
        if role != Qt.EditRole:
            return True
        item = self.data_items[index.row()]
        attribute_name = self.columns[index.column()]
        attribute_name = u'tags' if attribute_name == u'category' else attribute_name
        attribute_name = u'title' if attribute_name == u'name' else attribute_name
        attribute_name = u'subscribed' if attribute_name == u'votes' else attribute_name

        def on_row_update_results(response):
            if not response:
                return
            item_row = self.item_uid_map.get(get_item_uid(item))
            if item_row is None:
                return
            data_item_dict = index.model().data_items[item_row]
            data_item_dict.update(response)
            self.info_changed.emit([data_item_dict])

        TriblerNetworkRequest(
            f"metadata/{item['public_key']}/{item['id']}",
            on_row_update_results,
            method='PATCH',
            raw_data=json.dumps({attribute_name: new_value}),
        )

        # TODO: reload the whole row from DB instead of just changing the displayed value
        self.data_items[index.row()][self.columns[index.column()]] = new_value
        return True

    def on_new_entry_received(self, response):
        self.on_query_results(response, remote=True)
Exemple #16
0
class ChannelContentModel(RemoteTableModel):

    columns = [ACTION_BUTTONS, 'category', 'name', 'size', 'health', 'updated']
    column_headers = [
        '', '', tr('Name'),
        tr('Size'),
        tr('Health'),
        tr('Updated')
    ]
    unsortable_columns = ['status', 'state', ACTION_BUTTONS]
    column_flags = {
        'subscribed': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        'category': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        'name': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        'torrents': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        'size': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        'updated': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        'health': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        'votes': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        'state': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        'status': Qt.ItemIsEnabled | Qt.ItemIsSelectable,
        ACTION_BUTTONS: Qt.ItemIsEnabled | Qt.ItemIsSelectable,
    }

    column_width = {
        'state': lambda _: 100,
        'subscribed': lambda _: 100,
        'name': lambda table_width: table_width - 450,
        'action_buttons': lambda _: 50,
        'category': lambda _: 30,
    }

    column_tooltip_filters = {
        'state':
        lambda data: data,
        'votes':
        lambda data: get_votes_rating_description(data)
        if data is not None else None,
        'category':
        lambda data: data,
        'health':
        lambda data: f"{data}" +
        ('' if data == HEALTH_CHECKING else '\n(Click to recheck)'),
    }

    column_display_filters = {
        'size':
        lambda data: (format_size(float(data)) if data != '' else ''),
        'votes':
        format_votes,
        'updated':
        lambda timestamp: pretty_date(timestamp)
        if timestamp and timestamp > BITTORRENT_BIRTHDAY else 'N/A',
    }

    def __init__(
        self,
        channel_info=None,
        hide_xxx=None,
        exclude_deleted=None,
        subscribed_only=None,
        endpoint_url=None,
        text_filter='',
        type_filter=None,
    ):
        RemoteTableModel.__init__(self, parent=None)
        self.column_position = {name: i for i, name in enumerate(self.columns)}

        # Remote query (model) parameters
        self.hide_xxx = hide_xxx
        self.text_filter = text_filter
        self.subscribed_only = subscribed_only
        self.exclude_deleted = exclude_deleted
        self.type_filter = type_filter
        self.category_filter = None

        # Current channel attributes. This is intentionally NOT copied, so local changes
        # can propagate to the origin, e.g. parent channel.
        self.channel_info = channel_info or {
            "name": "My channels",
            "status": 123
        }

        self.endpoint_url_override = endpoint_url

        # Load the initial batch of entries
        self.perform_query()

    @property
    def edit_enabled(self):
        return False

    @property
    def endpoint_url(self):
        return self.endpoint_url_override or "channels/%s/%i" % (
            self.channel_info["public_key"],
            self.channel_info["id"],
        )

    def headerData(self, num, orientation, role=None):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self.column_headers[num]
        if role == Qt.InitialSortOrderRole and num != self.column_position.get(
                'name'):
            return Qt.DescendingOrder
        if role == Qt.TextAlignmentRole:
            return (Qt.AlignHCenter if num in [
                self.column_position.get('subscribed'),
                self.column_position.get('torrents')
            ] else Qt.AlignLeft)
        return super().headerData(num, orientation, role)

    def rowCount(self, *_, **__):
        return len(self.data_items)

    def columnCount(self, *_, **__):
        return len(self.columns)

    def flags(self, index):
        return self.column_flags[self.columns[index.column()]]

    def filter_item_txt(self, txt_filter, index, show_default=True):
        # ACHTUNG! Dumb workaround for some mysterious race condition
        try:
            item = self.data_items[index.row()]
        except IndexError:
            return ""

        column = self.columns[index.column()]
        data = item.get(column, '')

        # Print number of torrents in the channel for channel rows in the "size" column
        if (column == "size" and "torrents" not in self.columns
                and "torrents" in item
                and item["type"] in (CHANNEL_TORRENT, COLLECTION_NODE)):
            return item["torrents"]

        # 'subscribed' column gets special treatment in case of ToolTipRole, because
        # its tooltip uses information from both 'subscribed' and 'state' keys
        if (column == 'subscribed'
                and txt_filter == self.column_tooltip_filters
                and 'subscribed' in item and 'state' in item):
            state_message = f" ({item['state']})" if item[
                'state'] != CHANNEL_STATE.COMPLETE.value else ""
            tooltip_txt = (
                f"Subscribed.{state_message}\n(Click to unsubscribe)"
                if item['subscribed'] else
                "Not subscribed.\n(Click to subscribe)")
            return tooltip_txt

        if column in txt_filter:
            display_txt = txt_filter.get(column, str(data))(data)
        elif show_default:
            display_txt = data
        else:
            display_txt = None
        return display_txt

    def data(self, index, role):
        if role in (Qt.DisplayRole, Qt.EditRole):
            return self.filter_item_txt(self.column_display_filters, index)
        if role == Qt.ToolTipRole:
            return self.filter_item_txt(self.column_tooltip_filters,
                                        index,
                                        show_default=False)
        if role == Qt.TextAlignmentRole:
            if index.column() == self.column_position.get('votes', -1):
                return Qt.AlignLeft | Qt.AlignVCenter
            if index.column() == self.column_position.get('torrents', -1):
                return Qt.AlignHCenter | Qt.AlignVCenter
        return None

    def reset(self):
        self.item_uid_map.clear()
        super().reset()

    def update_node_info(self, update_dict):
        """
        This method updates/inserts rows based on updated_dict. It should be typically invoked
        by a signal from Events endpoint. One special case it when the channel_info of the model
        itself is updated. In that case, info_changed signal is emitted, so the controller/widget knows
        it is time to update the labels.
        """

        if (self.channel_info.get("public_key") ==
                update_dict.get("public_key") is not None
                and self.channel_info.get("id") == update_dict.get("id") is
                not None):
            self.channel_info.update(**update_dict)
            self.info_changed.emit([])
            return

        row = self.item_uid_map.get(get_item_uid(update_dict))
        if row is not None and row < len(self.data_items):
            self.data_items[row].update(**update_dict)
            self.dataChanged.emit(self.index(row, 0),
                                  self.index(row, len(self.columns)), [])

    def perform_query(self, **kwargs):
        """
        Fetch search results.
        """

        if self.type_filter is not None:
            kwargs.update({"metadata_type": self.type_filter})
        else:
            kwargs.update(
                {"metadata_type": [REGULAR_TORRENT, COLLECTION_NODE]})
        if self.subscribed_only is not None:
            kwargs.update({"subscribed": self.subscribed_only})
        if self.exclude_deleted is not None:
            kwargs.update({"exclude_deleted": self.exclude_deleted})
        if self.category_filter is not None:
            if self.category_filter == "Channels":
                kwargs.update({'metadata_type': 'channel'})
            else:
                kwargs.update({"category": self.category_filter})

        if "total" not in self.channel_info:
            # Only include total for the first query to the endpoint
            kwargs.update({"include_total": 1})

        super().perform_query(**kwargs)

    def setData(self, index, new_value, role=None):
        if role != Qt.EditRole:
            return True
        item = self.data_items[index.row()]
        attribute_name = self.columns[index.column()]
        attribute_name = 'tags' if attribute_name == 'category' else attribute_name
        attribute_name = 'title' if attribute_name == 'name' else attribute_name

        if attribute_name == 'subscribed':
            return True

        def on_row_update_results(response):
            if not response:
                return
            item_row = self.item_uid_map.get(get_item_uid(item))
            if item_row is None:
                return
            data_item_dict = index.model().data_items[item_row]
            data_item_dict.update(response)
            self.info_changed.emit([data_item_dict])

        TriblerNetworkRequest(
            f"metadata/{item['public_key']}/{item['id']}",
            on_row_update_results,
            method='PATCH',
            raw_data=json.dumps({attribute_name: new_value}),
        )

        # ACHTUNG: instead of reloading the whole row from DB, this line just changes the displayed value!
        self.data_items[index.row()][self.columns[index.column()]] = new_value
        return True

    def on_new_entry_received(self, response):
        self.on_query_results(response, remote=True)
Exemple #17
0
 def update_item(self):
     self.setText(0, self.file_info["name"])
     self.setText(1, format_size(float(self.file_info["size"])))
     self.setText(2, f"{self.file_info['progress']:.1%}")
     self.setText(3, "yes" if self.file_info["included"] else "no")
     self.setData(0, Qt.UserRole, self.file_info)
Exemple #18
0
 def tickStrings(self, values, scale, spacing):
     return [format_size(value, precision=3) for value in values]