mirror of
https://github.com/transmission/transmission.git
synced 2025-12-19 18:08:31 +00:00
perf: faster file-lists in the GTK client's details dialog (#2993)
* perf: faster file tree building in GTK client * perf: faster GTK client FileList * use a std::map for faster node lookup when building a file tree * use tr_get_mime_type_for_filename() for mime-type lookup * remove unnecessary layer of indirection when building the keys for lookuops in the mime-type icon cache * be more careful to only call operator= on proxies whose values have actually changed, since that assignment is expensive
This commit is contained in:
194
gtk/FileList.cc
194
gtk/FileList.cc
@@ -6,7 +6,11 @@
|
||||
#include <climits> /* INT_MAX */
|
||||
#include <cstddef>
|
||||
#include <cstring> // strchr
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <glibmm.h>
|
||||
#include <glibmm/i18n.h>
|
||||
@@ -23,6 +27,8 @@
|
||||
#include "Session.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -145,51 +151,32 @@ bool refreshFilesForeach(
|
||||
bool const is_file = iter->children().empty();
|
||||
|
||||
auto const old_enabled = iter->get_value(file_cols.enabled);
|
||||
auto const old_priority = iter->get_value(file_cols.priority);
|
||||
auto const index = iter->get_value(file_cols.index);
|
||||
auto const old_have = iter->get_value(file_cols.have);
|
||||
auto const size = iter->get_value(file_cols.size);
|
||||
auto const old_prog = iter->get_value(file_cols.prog);
|
||||
auto const old_priority = iter->get_value(file_cols.priority);
|
||||
auto const old_progress = iter->get_value(file_cols.prog);
|
||||
auto const old_size = iter->get_value(file_cols.size);
|
||||
|
||||
auto new_enabled = old_enabled;
|
||||
auto new_have = old_have;
|
||||
auto new_priority = old_priority;
|
||||
auto new_progress = old_progress;
|
||||
auto new_size = old_size;
|
||||
|
||||
if (is_file)
|
||||
{
|
||||
auto const* tor = refresh_data.tor;
|
||||
auto const file = tr_torrentFile(tor, index);
|
||||
int const enabled = file.wanted;
|
||||
int const priority = file.priority;
|
||||
auto const progress = file.progress;
|
||||
uint64_t const have = file.have;
|
||||
int const prog = std::clamp(int(100 * progress), 0, 100);
|
||||
auto const index = iter->get_value(file_cols.index);
|
||||
auto const file = tr_torrentFile(refresh_data.tor, index);
|
||||
|
||||
if (priority != old_priority || enabled != old_enabled || have != old_have || prog != old_prog)
|
||||
{
|
||||
/* Changing a value in the sort column can trigger a resort
|
||||
* which breaks this foreach () call. (See #3529)
|
||||
* As a workaround: if that's about to happen, temporarily disable
|
||||
* sorting until we finish walking the tree. */
|
||||
if (!refresh_data.resort_needed &&
|
||||
(((refresh_data.sort_column_id == file_cols.priority.index()) && (priority != old_priority)) ||
|
||||
((refresh_data.sort_column_id == file_cols.enabled.index()) && (enabled != old_enabled))))
|
||||
{
|
||||
refresh_data.resort_needed = true;
|
||||
|
||||
store->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);
|
||||
}
|
||||
|
||||
(*iter)[file_cols.priority] = priority;
|
||||
(*iter)[file_cols.enabled] = enabled;
|
||||
(*iter)[file_cols.have] = have;
|
||||
(*iter)[file_cols.prog] = prog;
|
||||
(*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), prog);
|
||||
}
|
||||
new_enabled = file.wanted;
|
||||
new_priority = file.priority;
|
||||
new_have = file.have;
|
||||
new_progress = std::clamp(int(100 * file.progress), 0, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t sub_size = 0;
|
||||
uint64_t have = 0;
|
||||
int prog;
|
||||
int enabled = NOT_SET;
|
||||
int priority = NOT_SET;
|
||||
new_size = 0;
|
||||
new_enabled = NOT_SET;
|
||||
new_priority = NOT_SET;
|
||||
|
||||
/* since gtk_tree_model_foreach() is depth-first, we can
|
||||
* get the `sub' info by walking the immediate children */
|
||||
@@ -203,43 +190,75 @@ bool refreshFilesForeach(
|
||||
|
||||
if ((child_enabled != false) && (child_enabled != NOT_SET))
|
||||
{
|
||||
sub_size += child_size;
|
||||
have += child_have;
|
||||
new_size += child_size;
|
||||
new_have += child_have;
|
||||
}
|
||||
|
||||
if (enabled == NOT_SET)
|
||||
if (new_enabled == NOT_SET)
|
||||
{
|
||||
enabled = child_enabled;
|
||||
new_enabled = child_enabled;
|
||||
}
|
||||
else if (enabled != child_enabled)
|
||||
else if (new_enabled != child_enabled)
|
||||
{
|
||||
enabled = MIXED;
|
||||
new_enabled = MIXED;
|
||||
}
|
||||
|
||||
if (priority == NOT_SET)
|
||||
if (new_priority == NOT_SET)
|
||||
{
|
||||
priority = child_priority;
|
||||
new_priority = child_priority;
|
||||
}
|
||||
else if (priority != child_priority)
|
||||
else if (new_priority != child_priority)
|
||||
{
|
||||
priority = MIXED;
|
||||
new_priority = MIXED;
|
||||
}
|
||||
}
|
||||
|
||||
prog = sub_size != 0 ? (int)(100.0 * have / sub_size) : 1;
|
||||
new_progress = new_size != 0 ? (int)(100.0 * new_have / new_size) : 1;
|
||||
}
|
||||
|
||||
if (size != sub_size || have != old_have || priority != old_priority || enabled != old_enabled || prog != old_prog)
|
||||
if (new_priority != old_priority || new_enabled != old_enabled)
|
||||
{
|
||||
/* Changing a value in the sort column can trigger a resort
|
||||
* which breaks this foreach () call. (See #3529)
|
||||
* As a workaround: if that's about to happen, temporarily disable
|
||||
* sorting until we finish walking the tree. */
|
||||
if (!refresh_data.resort_needed &&
|
||||
(((refresh_data.sort_column_id == file_cols.priority.index()) && (new_priority != old_priority)) ||
|
||||
((refresh_data.sort_column_id == file_cols.enabled.index()) && (new_enabled != old_enabled))))
|
||||
{
|
||||
(*iter)[file_cols.size] = sub_size;
|
||||
(*iter)[file_cols.size_str] = tr_strlsize(sub_size);
|
||||
(*iter)[file_cols.have] = have;
|
||||
(*iter)[file_cols.priority] = priority;
|
||||
(*iter)[file_cols.enabled] = enabled;
|
||||
(*iter)[file_cols.prog] = prog;
|
||||
(*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), prog);
|
||||
refresh_data.resort_needed = true;
|
||||
|
||||
store->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);
|
||||
}
|
||||
}
|
||||
|
||||
if (new_enabled != old_enabled)
|
||||
{
|
||||
(*iter)[file_cols.enabled] = new_enabled;
|
||||
}
|
||||
|
||||
if (new_priority != old_priority)
|
||||
{
|
||||
(*iter)[file_cols.priority] = new_priority;
|
||||
}
|
||||
|
||||
if (new_size != old_size)
|
||||
{
|
||||
(*iter)[file_cols.size] = new_size;
|
||||
(*iter)[file_cols.size_str] = tr_strlsize(new_size);
|
||||
}
|
||||
|
||||
if (new_have != old_have)
|
||||
{
|
||||
(*iter)[file_cols.have] = new_have;
|
||||
}
|
||||
|
||||
if (new_progress != old_progress)
|
||||
{
|
||||
(*iter)[file_cols.prog] = new_progress;
|
||||
(*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), new_progress);
|
||||
}
|
||||
|
||||
return false; /* keep walking */
|
||||
}
|
||||
|
||||
@@ -270,9 +289,7 @@ void gtr_tree_model_foreach_postorder(Glib::RefPtr<Gtk::TreeModel> const& model,
|
||||
|
||||
void FileList::Impl::refresh()
|
||||
{
|
||||
tr_torrent* tor = core_->find_torrent(torrent_id_);
|
||||
|
||||
if (tor == nullptr)
|
||||
if (tr_torrent* tor = core_->find_torrent(torrent_id_); tor == nullptr)
|
||||
{
|
||||
widget_.clear();
|
||||
}
|
||||
@@ -424,14 +441,14 @@ void buildTree(FileRowNode& node, build_data& build)
|
||||
auto const& child_data = node.data();
|
||||
bool const isLeaf = node.child_count() == 0;
|
||||
|
||||
auto const mime_type = isLeaf ? gtr_get_mime_type_from_filename(child_data.name) : DirectoryMimeType;
|
||||
auto const mime_type = isLeaf ? tr_get_mime_type_for_filename(child_data.name.raw()) : DirectoryMimeType;
|
||||
auto const icon = gtr_get_mime_type_icon(mime_type, Gtk::ICON_SIZE_MENU, *build.w);
|
||||
auto const file = isLeaf ? tr_torrentFile(build.tor, child_data.index) : tr_file_view{};
|
||||
int const priority = isLeaf ? file.priority : 0;
|
||||
bool const enabled = isLeaf ? file.wanted : true;
|
||||
auto name_esc = Glib::Markup::escape_text(child_data.name);
|
||||
|
||||
auto const child_iter = build.store->append(build.iter->children());
|
||||
auto const child_iter = build.store->prepend(build.iter->children());
|
||||
(*child_iter)[file_cols.index] = child_data.index;
|
||||
(*child_iter)[file_cols.label] = child_data.name;
|
||||
(*child_iter)[file_cols.label_esc] = name_esc;
|
||||
@@ -449,21 +466,6 @@ void buildTree(FileRowNode& node, build_data& build)
|
||||
}
|
||||
}
|
||||
|
||||
FileRowNode* find_child(FileRowNode* parent, Glib::ustring const& name)
|
||||
{
|
||||
for (auto* child = parent->first_child(); child != nullptr; child = child->next_sibling())
|
||||
{
|
||||
auto const& child_data = child->data();
|
||||
|
||||
if (child_data.name == name)
|
||||
{
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void FileList::set_torrent(int torrent_id)
|
||||
@@ -471,6 +473,15 @@ void FileList::set_torrent(int torrent_id)
|
||||
impl_->set_torrent(torrent_id);
|
||||
}
|
||||
|
||||
struct PairHash
|
||||
{
|
||||
template<typename T1, typename T2>
|
||||
auto operator()(std::pair<T1, T2> const& pair) const
|
||||
{
|
||||
return std::hash<T1>{}(pair.first) ^ std::hash<T2>{}(pair.second);
|
||||
}
|
||||
};
|
||||
|
||||
void FileList::Impl::set_torrent(int torrentId)
|
||||
{
|
||||
/* unset the old fields */
|
||||
@@ -486,32 +497,34 @@ void FileList::Impl::set_torrent(int torrentId)
|
||||
if (auto* const tor = core_->find_torrent(torrent_id_); tor != nullptr)
|
||||
{
|
||||
// build a GNode tree of the files
|
||||
FileRowNode root;
|
||||
auto root = FileRowNode{};
|
||||
auto& root_data = root.data();
|
||||
root_data.name = tr_torrentName(tor);
|
||||
root_data.index = -1;
|
||||
root_data.length = 0;
|
||||
|
||||
auto nodes = std::unordered_map<std::pair<FileRowNode* /*parent*/, std::string_view>, FileRowNode*, PairHash>{};
|
||||
|
||||
for (tr_file_index_t i = 0, n_files = tr_torrentFileCount(tor); i < n_files; ++i)
|
||||
{
|
||||
auto* parent = &root;
|
||||
auto const file = tr_torrentFile(tor, i);
|
||||
|
||||
for (char const *last = file.name, *next = strchr(last, '/'); last != nullptr;
|
||||
last = next != nullptr ? next + 1 : nullptr, next = last != nullptr ? strchr(last, '/') : nullptr)
|
||||
auto path = std::string_view{ file.name };
|
||||
auto token = std::string_view{};
|
||||
while (tr_strvSep(&path, &token, '/'))
|
||||
{
|
||||
bool const isLeaf = next == nullptr;
|
||||
auto name = Glib::ustring(isLeaf ? last : std::string(last, next - last));
|
||||
auto* node = find_child(parent, name);
|
||||
auto*& node = nodes[std::make_pair(parent, token)];
|
||||
|
||||
if (node == nullptr)
|
||||
{
|
||||
node = new FileRowNode();
|
||||
auto& row = node->data();
|
||||
row.name = std::move(name);
|
||||
row.index = isLeaf ? (int)i : -1;
|
||||
row.length = isLeaf ? file.length : 0;
|
||||
parent->append(*node);
|
||||
auto const is_leaf = std::empty(path);
|
||||
|
||||
node = parent->prepend_data({});
|
||||
auto& node_data = node->data();
|
||||
node_data.name = std::string{ token };
|
||||
node_data.index = is_leaf ? (int)i : -1;
|
||||
node_data.length = is_leaf ? file.length : 0;
|
||||
}
|
||||
|
||||
parent = node;
|
||||
@@ -537,7 +550,8 @@ void FileList::Impl::set_torrent(int torrentId)
|
||||
/* set default sort by label */
|
||||
store_->set_sort_column(file_cols.label, Gtk::SORT_ASCENDING);
|
||||
|
||||
view_->expand_all();
|
||||
view_->expand_row(Gtk::TreeModel::Path("0"), false);
|
||||
// view_->expand_all();
|
||||
}
|
||||
|
||||
/***
|
||||
@@ -731,7 +745,7 @@ bool FileList::Impl::on_rename_done_idle(Glib::ustring const& path_string, Glib:
|
||||
if (auto const iter = store_->get_iter(path_string); iter)
|
||||
{
|
||||
bool const isLeaf = iter->children().empty();
|
||||
auto const mime_type = isLeaf ? gtr_get_mime_type_from_filename(newname) : DirectoryMimeType;
|
||||
auto const mime_type = isLeaf ? tr_get_mime_type_for_filename(newname.raw()) : DirectoryMimeType;
|
||||
auto const icon = gtr_get_mime_type_icon(mime_type, Gtk::ICON_SIZE_MENU, *view_);
|
||||
|
||||
(*iter)[file_cols.label] = newname;
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <glibmm.h>
|
||||
#include <giomm.h>
|
||||
@@ -17,8 +19,8 @@
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
Glib::ustring const DirectoryMimeType = "folder"s;
|
||||
Glib::ustring const UnknownMimeType = "unknown"s;
|
||||
std::string_view const DirectoryMimeType = "folder"sv;
|
||||
std::string_view const UnknownMimeType = "unknown"sv;
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -29,7 +31,7 @@ struct IconCache
|
||||
{
|
||||
Glib::RefPtr<Gtk::IconTheme> icon_theme;
|
||||
int icon_size;
|
||||
std::unordered_map<std::string, Glib::RefPtr<Gdk::Pixbuf>> cache;
|
||||
std::map<std::string, Glib::RefPtr<Gdk::Pixbuf>, std::less<>> cache;
|
||||
};
|
||||
|
||||
std::array<std::unique_ptr<IconCache>, 7> icon_cache;
|
||||
@@ -58,28 +60,6 @@ std::unique_ptr<IconCache> icon_cache_new(Gtk::Widget& for_widget, Gtk::IconSize
|
||||
return icons;
|
||||
}
|
||||
|
||||
std::string _icon_cache_get_icon_key(Glib::RefPtr<Gio::Icon> const& icon)
|
||||
{
|
||||
std::string key;
|
||||
|
||||
if (auto const* const ticon = dynamic_cast<Gio::ThemedIcon*>(gtr_get_ptr(icon)); ticon != nullptr)
|
||||
{
|
||||
std::ostringstream names;
|
||||
for (auto const& name : ticon->get_names())
|
||||
{
|
||||
names << name << ',';
|
||||
}
|
||||
|
||||
key = names.str();
|
||||
}
|
||||
else if (auto* const ficon = dynamic_cast<Gio::FileIcon*>(gtr_get_ptr(icon)); ficon != nullptr)
|
||||
{
|
||||
key = ficon->get_file()->get_path();
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> get_themed_icon_pixbuf(Gio::ThemedIcon& icon, int size, Gtk::IconTheme& icon_theme)
|
||||
{
|
||||
auto const icon_names = icon.get_names();
|
||||
@@ -134,25 +114,21 @@ Glib::RefPtr<Gdk::Pixbuf> _get_icon_pixbuf(Glib::RefPtr<Gio::Icon> const& icon,
|
||||
return {};
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> icon_cache_get_mime_type_icon(IconCache& icons, Glib::ustring const& mime_type)
|
||||
Glib::RefPtr<Gdk::Pixbuf> icon_cache_get_mime_type_icon(IconCache& icons, std::string_view mime_type)
|
||||
{
|
||||
auto icon = Gio::content_type_get_icon(mime_type);
|
||||
auto key = _icon_cache_get_icon_key(icon);
|
||||
if (key.empty())
|
||||
auto& cache = icons.cache;
|
||||
|
||||
if (auto mime_it = cache.find(mime_type); mime_it != std::end(cache))
|
||||
{
|
||||
key = VoidPixbufKey;
|
||||
return mime_it->second;
|
||||
}
|
||||
|
||||
if (auto pixbuf_it = icons.cache.find(key); pixbuf_it != icons.cache.end())
|
||||
{
|
||||
return pixbuf_it->second;
|
||||
}
|
||||
|
||||
auto const pixbuf = _get_icon_pixbuf(icon, icons.icon_size, *gtr_get_ptr(icons.icon_theme));
|
||||
|
||||
auto mime_type_str = std::string{ mime_type };
|
||||
auto icon = Gio::content_type_get_icon(mime_type_str);
|
||||
auto pixbuf = _get_icon_pixbuf(icon, icons.icon_size, *gtr_get_ptr(icons.icon_theme));
|
||||
if (pixbuf != nullptr)
|
||||
{
|
||||
icons.cache.try_emplace(key, pixbuf);
|
||||
cache.try_emplace(std::move(mime_type_str), pixbuf);
|
||||
}
|
||||
|
||||
return pixbuf;
|
||||
@@ -160,10 +136,7 @@ Glib::RefPtr<Gdk::Pixbuf> icon_cache_get_mime_type_icon(IconCache& icons, Glib::
|
||||
|
||||
} // namespace
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(
|
||||
Glib::ustring const& mime_type,
|
||||
Gtk::IconSize icon_size,
|
||||
Gtk::Widget& for_widget)
|
||||
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(std::string_view mime_type, Gtk::IconSize icon_size, Gtk::Widget& for_widget)
|
||||
{
|
||||
int n;
|
||||
|
||||
@@ -205,9 +178,3 @@ Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(
|
||||
|
||||
return icon_cache_get_mime_type_icon(*icon_cache[n], mime_type);
|
||||
}
|
||||
|
||||
Glib::ustring gtr_get_mime_type_from_filename(std::string const& file)
|
||||
{
|
||||
bool result_uncertain;
|
||||
return Gio::content_type_guess(file, {}, result_uncertain);
|
||||
}
|
||||
|
||||
@@ -7,16 +7,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <gtkmm.h>
|
||||
|
||||
extern Glib::ustring const DirectoryMimeType;
|
||||
extern Glib::ustring const UnknownMimeType;
|
||||
extern std::string_view const DirectoryMimeType;
|
||||
extern std::string_view const UnknownMimeType;
|
||||
|
||||
Glib::ustring gtr_get_mime_type_from_filename(std::string const& file);
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(
|
||||
Glib::ustring const& mime_type,
|
||||
Gtk::IconSize icon_size,
|
||||
Gtk::Widget& for_widget);
|
||||
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(std::string_view mime_type, Gtk::IconSize icon_size, Gtk::Widget& for_widget);
|
||||
|
||||
@@ -350,7 +350,7 @@ namespace
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> get_icon(tr_torrent const* tor, Gtk::IconSize icon_size, Gtk::Widget& for_widget)
|
||||
{
|
||||
Glib::ustring mime_type;
|
||||
auto mime_type = std::string_view{};
|
||||
|
||||
if (auto const n_files = tr_torrentFileCount(tor); n_files == 0)
|
||||
{
|
||||
@@ -364,7 +364,7 @@ Glib::RefPtr<Gdk::Pixbuf> get_icon(tr_torrent const* tor, Gtk::IconSize icon_siz
|
||||
{
|
||||
auto const* const name = tr_torrentFile(tor, 0).name;
|
||||
|
||||
mime_type = strchr(name, '/') != nullptr ? DirectoryMimeType : gtr_get_mime_type_from_filename(name);
|
||||
mime_type = strchr(name, '/') != nullptr ? DirectoryMimeType : tr_get_mime_type_for_filename(name);
|
||||
}
|
||||
|
||||
return gtr_get_mime_type_icon(mime_type, icon_size, for_widget);
|
||||
|
||||
Reference in New Issue
Block a user