transmission_gobject/
file_model.rs1use std::cell::{Cell, RefCell};
2
3use gio::prelude::*;
4use gio::subclass::prelude::*;
5use glib::Properties;
6use indexmap::map::IndexMap;
7use transmission_client::TorrentFiles;
8
9use crate::{TrFile, TrTorrent};
10
11mod imp {
12 use super::*;
13
14 #[derive(Debug, Default, Properties)]
15 #[properties(wrapper_type = super::TrFileModel)]
16 pub struct TrFileModel {
17 #[property(get, nullable)]
19 pub top_level: RefCell<Option<TrFile>>,
20 #[property(get)]
22 pub is_ready: Cell<bool>,
23
24 pub map: RefCell<IndexMap<String, TrFile>>,
26 }
27
28 #[glib::object_subclass]
29 impl ObjectSubclass for TrFileModel {
30 const NAME: &'static str = "TrFileModel";
31 type ParentType = glib::Object;
32 type Type = super::TrFileModel;
33 type Interfaces = (gio::ListModel,);
34 }
35
36 #[glib::derived_properties]
37 impl ObjectImpl for TrFileModel {}
38
39 impl ListModelImpl for TrFileModel {
40 fn item_type(&self) -> glib::Type {
41 TrFile::static_type()
42 }
43
44 fn n_items(&self) -> u32 {
45 self.map.borrow().len() as u32
46 }
47
48 fn item(&self, position: u32) -> Option<glib::Object> {
49 self.map
50 .borrow()
51 .get_index(position.try_into().unwrap())
52 .map(|(_, o)| o.clone().upcast::<glib::Object>())
53 }
54 }
55
56 impl TrFileModel {
57 pub fn add_file(&self, file: &TrFile, torrent: &TrTorrent) {
58 if self.obj().file_by_name(&file.name()).is_some() {
59 warn!("File {:?} already exists in model", file.name());
60 return;
61 }
62
63 let mut map = self.map.borrow_mut();
64 let full_path = file.name();
65
66 let mut folder_names = Vec::new();
70 let folder_name = if full_path.contains('/') {
71 let slashes = full_path.match_indices('/');
72 let folder_name = full_path[..slashes.clone().next_back().unwrap().0].to_string();
73
74 for slash in slashes {
75 let folder_name = full_path[..slash.0].to_string();
76 folder_names.push(folder_name);
77 }
78
79 Some(folder_name)
80 } else {
81 self.top_level.borrow_mut().replace(file.clone());
83 self.obj().notify_top_level();
84 None
85 };
86
87 let mut parent: Option<TrFile> = None;
89 for folder_name in folder_names {
90 let folder = if let Some(folder) = map.get(&folder_name) {
91 folder.clone()
92 } else {
93 let folder = TrFile::new_folder(&folder_name, torrent);
94
95 if let Some(parent) = parent {
97 parent.add_related(&folder);
98 } else {
99 self.top_level.borrow_mut().replace(folder.clone());
101 self.obj().notify_top_level();
102 }
103
104 map.insert(folder_name.clone(), folder.clone());
105 folder
106 };
107
108 parent = Some(folder);
109 }
110
111 map.insert(full_path.clone(), file.clone());
114 let pos = (map.len() - 1) as u32;
115 self.obj().items_changed(pos, 0, 1);
116
117 if let Some(folder_name) = folder_name {
118 map.get_mut(&folder_name).unwrap().add_related(file);
120 }
121 }
122 }
123}
124
125glib::wrapper! {
126 pub struct TrFileModel(ObjectSubclass<imp::TrFileModel>) @implements gio::ListModel;
127}
128
129impl TrFileModel {
130 pub(crate) fn refresh_files(&self, rpc_files: &TorrentFiles, torrent: &TrTorrent) {
131 let is_initial = self.top_level().is_none();
132
133 for (index, rpc_file) in rpc_files.files.iter().enumerate() {
134 let rpc_file_stat = rpc_files.file_stats.get(index).cloned().unwrap_or_default();
135 if let Some(file) = self.file_by_name(&rpc_file.name) {
136 file.refresh_values(&rpc_file_stat);
137 } else {
138 let file = TrFile::from_rpc_file(index.try_into().unwrap(), rpc_file, torrent);
139 file.refresh_values(&rpc_file_stat);
140
141 self.imp().add_file(&file, torrent);
142 }
143 }
144
145 if is_initial && self.top_level().is_some() {
146 self.imp().is_ready.set(true);
147 self.notify_is_ready();
148 }
149 }
150
151 pub(crate) fn related_files_by_path(&self, path: &str) -> Vec<TrFile> {
152 let imp = self.imp();
153 let mut result = Vec::new();
154
155 for (file_path, file) in &*imp.map.borrow() {
156 if file_path.contains(path) && !file.is_folder() {
157 result.push(file.clone());
158 }
159 }
160
161 result
162 }
163
164 pub fn file_by_name(&self, name: &str) -> Option<TrFile> {
166 self.imp()
167 .map
168 .borrow()
169 .get(name)
170 .map(|o| o.clone().downcast().unwrap())
171 }
172
173 pub fn parent(&self, folder: &TrFile) -> Option<TrFile> {
175 let folder_name = folder.name();
176 if folder_name.contains('/') {
177 let slashes = folder_name.match_indices('/');
178 let parent_name = folder.name()[0..slashes.clone().next_back().unwrap().0].to_string();
179
180 let parent = self.file_by_name(&parent_name);
181 return parent;
182 }
183 None
184 }
185}
186
187impl Default for TrFileModel {
188 fn default() -> Self {
189 glib::Object::new()
190 }
191}