transmission_gobject/
file.rs1use std::cell::{Cell, OnceCell, RefCell};
2use std::collections::{HashMap, HashSet};
3
4use gio::prelude::*;
5use glib::subclass::prelude::{ObjectSubclass, *};
6use glib::{Properties, clone};
7use transmission_client::{File, FileStat};
8
9use crate::{TrRelatedModel, TrTorrent};
10
11mod imp {
12 use super::*;
13
14 #[derive(Debug, Default, Properties)]
15 #[properties(wrapper_type = super::TrFile)]
16 pub struct TrFile {
17 #[property(get, set, construct_only)]
18 torrent: OnceCell<TrTorrent>,
19
20 #[property(get, set, construct_only)]
21 id: Cell<i32>,
22 #[property(get, set, construct_only)]
23 name: OnceCell<String>,
24 #[property(get, set, construct_only)]
25 title: OnceCell<String>,
26 #[property(get, set, construct_only)]
27 is_folder: Cell<bool>,
28 #[property(get)]
29 pub bytes_completed: Cell<i64>,
30 #[property(get, set, construct_only)]
31 length: Cell<i64>,
32 #[property(get, set = Self::set_wanted)]
33 pub wanted: Cell<bool>,
34 #[property(get)]
35 wanted_inconsistent: Cell<bool>,
36 #[property(get)]
37 related: TrRelatedModel,
38
39 related_wanted: RefCell<HashSet<String>>,
40 related_length: RefCell<HashMap<String, i64>>,
41 related_bytes_completed: RefCell<HashMap<String, i64>>,
42 }
43
44 #[glib::object_subclass]
45 impl ObjectSubclass for TrFile {
46 const NAME: &'static str = "TrFile";
47 type ParentType = glib::Object;
48 type Type = super::TrFile;
49 }
50
51 #[glib::derived_properties]
52 impl ObjectImpl for TrFile {}
53
54 impl TrFile {
55 fn set_wanted(&self, wanted: bool) {
56 let fut = clone!(
57 #[weak(rename_to = this)]
58 self,
59 async move {
60 let ids = if this.obj().is_folder() {
62 let path = this.obj().name();
63
64 let files = this.obj().torrent().files().related_files_by_path(&path);
66
67 let mut ids = Vec::new();
68 for file in files {
69 ids.push(file.id());
70 }
71
72 ids
73 } else {
74 vec![this.obj().id()]
75 };
76
77 if wanted {
78 this.torrent
79 .get()
80 .unwrap()
81 .set_wanted_files(ids)
82 .await
83 .unwrap();
84 } else {
85 this.torrent
86 .get()
87 .unwrap()
88 .set_unwanted_files(ids)
89 .await
90 .unwrap();
91 }
92
93 this.wanted.set(wanted);
94 this.obj().notify_wanted();
95 }
96 );
97 glib::spawn_future_local(fut);
98 }
99
100 pub fn find_title(name: &str) -> String {
101 if !name.contains('/') {
102 return name.to_string();
103 }
104
105 let slashes = name.match_indices('/');
106 name[(slashes.clone().next_back().unwrap().0) + 1..name.len()].to_string()
107 }
108
109 pub fn update_related_wanted(&self, related_file: &super::TrFile) {
111 assert!(self.obj().is_folder());
112
113 if related_file.wanted() && !related_file.wanted_inconsistent() {
114 self.related_wanted.borrow_mut().insert(related_file.name());
115 } else {
116 self.related_wanted
117 .borrow_mut()
118 .remove(&related_file.name());
119 }
120
121 let files_count = self.obj().related().n_items() as usize;
124 let wanted_count = self.related_wanted.borrow().len();
125
126 if files_count == wanted_count {
127 self.wanted.set(true);
128 self.wanted_inconsistent.set(false);
129 } else if wanted_count == 0 {
130 self.wanted.set(false);
131 self.wanted_inconsistent.set(false);
132 } else {
133 self.wanted.set(true);
134 self.wanted_inconsistent.set(true);
135 }
136
137 self.obj().notify_wanted();
138 self.obj().notify_wanted_inconsistent();
139 }
140
141 pub fn update_related_length(&self, related_file: &super::TrFile) {
143 assert!(self.obj().is_folder());
144
145 let obj = self.obj();
146 let mut related_length = self.related_length.borrow_mut();
147
148 let mut value_changed = false;
149 let mut previous_value: i64 = 0;
150
151 if let Some(length) = related_length.get(&related_file.name()) {
152 if length != &related_file.length() {
153 value_changed = true;
154 previous_value = *length;
155 }
156 } else {
157 related_length.insert(related_file.name(), related_file.length());
158 self.length.set(obj.length() + related_file.length());
159 self.obj().notify_length();
160 }
161
162 if value_changed {
163 related_length.insert(related_file.name(), related_file.length());
164 self.length.set(obj.length() - previous_value);
165 self.length.set(obj.length() + related_file.length());
166 self.obj().notify_length();
167 }
168 }
169
170 pub fn update_related_bytes_completed(&self, related_file: &super::TrFile) {
173 assert!(self.obj().is_folder());
174
175 let obj = self.obj();
176 let mut related_bytes_completed = self.related_bytes_completed.borrow_mut();
177
178 let mut value_changed = false;
179 let mut previous_value: i64 = 0;
180
181 if let Some(bytes_completed) = related_bytes_completed.get(&related_file.name()) {
182 if bytes_completed != &related_file.bytes_completed() {
183 value_changed = true;
184 previous_value = *bytes_completed;
185 }
186 } else {
187 related_bytes_completed.insert(related_file.name(), related_file.bytes_completed());
188 self.bytes_completed
189 .set(obj.bytes_completed() + related_file.bytes_completed());
190 self.obj().notify_bytes_completed();
191 }
192
193 if value_changed {
194 related_bytes_completed.insert(related_file.name(), related_file.bytes_completed());
195 self.bytes_completed
196 .set(obj.bytes_completed() - previous_value);
197 self.bytes_completed
198 .set(obj.bytes_completed() + related_file.bytes_completed());
199 self.obj().notify_bytes_completed();
200 }
201 }
202 }
203}
204
205glib::wrapper! {
206 pub struct TrFile(ObjectSubclass<imp::TrFile>);
207}
208
209impl TrFile {
210 pub(crate) fn from_rpc_file(id: i32, rpc_file: &File, torrent: &TrTorrent) -> Self {
211 let name = rpc_file.name.clone();
212 let title = imp::TrFile::find_title(&name);
213
214 glib::Object::builder()
215 .property("id", id)
216 .property("name", &name)
217 .property("title", &title)
218 .property("length", rpc_file.length)
219 .property("is-folder", false)
220 .property("torrent", torrent)
221 .build()
222 }
223
224 pub(crate) fn new_folder(name: &str, torrent: &TrTorrent) -> Self {
225 let title = imp::TrFile::find_title(name);
226
227 glib::Object::builder()
228 .property("id", -1)
229 .property("name", name)
230 .property("title", &title)
231 .property("is-folder", true)
232 .property("torrent", torrent)
233 .build()
234 }
235
236 pub(crate) fn add_related(&self, file: &TrFile) {
238 assert!(self.is_folder());
239 let imp = self.imp();
240
241 self.related().add_file(file);
242
243 file.connect_notify_local(
244 Some("wanted"),
245 clone!(
246 #[weak(rename_to = this)]
247 imp,
248 move |file, _| {
249 this.update_related_wanted(file);
250 }
251 ),
252 );
253 file.connect_notify_local(
254 Some("wanted-inconsistent"),
255 clone!(
256 #[weak(rename_to = this)]
257 imp,
258 move |file, _| {
259 this.update_related_wanted(file);
260 }
261 ),
262 );
263 imp.update_related_wanted(file);
264
265 file.connect_notify_local(
266 Some("length"),
267 clone!(
268 #[weak(rename_to = this)]
269 imp,
270 move |file, _| {
271 this.update_related_length(file);
272 }
273 ),
274 );
275 imp.update_related_length(file);
276
277 file.connect_notify_local(
278 Some("bytes-completed"),
279 clone!(
280 #[weak(rename_to = this)]
281 imp,
282 move |file, _| {
283 this.update_related_bytes_completed(file);
284 }
285 ),
286 );
287 imp.update_related_bytes_completed(file);
288 }
289
290 pub(crate) fn refresh_values(&self, rpc_file_stat: &FileStat) {
292 assert!(!self.is_folder());
293 let imp = self.imp();
294
295 if imp.bytes_completed.get() != rpc_file_stat.bytes_completed {
297 imp.bytes_completed.set(rpc_file_stat.bytes_completed);
298 self.notify_bytes_completed();
299 }
300
301 if imp.wanted.get() != rpc_file_stat.wanted {
303 imp.wanted.set(rpc_file_stat.wanted);
304 self.notify_wanted();
305 }
306 }
307}