1 | /****************************************************************************** |
---|
2 | * $Id: tr-core.c 13797 2013-01-17 00:45:31Z jordan $ |
---|
3 | * |
---|
4 | * Copyright (c) Transmission authors and contributors |
---|
5 | * |
---|
6 | * Permission is hereby granted, free of charge, to any person obtaining a |
---|
7 | * copy of this software and associated documentation files (the "Software"), |
---|
8 | * to deal in the Software without restriction, including without limitation |
---|
9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
---|
10 | * and/or sell copies of the Software, and to permit persons to whom the |
---|
11 | * Software is furnished to do so, subject to the following conditions: |
---|
12 | * |
---|
13 | * The above copyright notice and this permission notice shall be included in |
---|
14 | * all copies or substantial portions of the Software. |
---|
15 | * |
---|
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
---|
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
---|
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
---|
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
---|
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
---|
21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
---|
22 | * DEALINGS IN THE SOFTWARE. |
---|
23 | *****************************************************************************/ |
---|
24 | |
---|
25 | #include <math.h> /* pow () */ |
---|
26 | #include <string.h> /* strcmp, strlen */ |
---|
27 | |
---|
28 | #include <gtk/gtk.h> |
---|
29 | #include <glib/gi18n.h> |
---|
30 | #include <gio/gio.h> |
---|
31 | |
---|
32 | #include <event2/buffer.h> |
---|
33 | |
---|
34 | #include <libtransmission/transmission.h> |
---|
35 | #include <libtransmission/rpcimpl.h> |
---|
36 | #include <libtransmission/utils.h> /* tr_free */ |
---|
37 | #include <libtransmission/variant.h> |
---|
38 | |
---|
39 | #include "actions.h" |
---|
40 | #include "conf.h" |
---|
41 | #include "notify.h" |
---|
42 | #include "tr-core.h" |
---|
43 | #include "tr-prefs.h" |
---|
44 | #include "util.h" |
---|
45 | |
---|
46 | /*** |
---|
47 | **** |
---|
48 | ***/ |
---|
49 | |
---|
50 | enum |
---|
51 | { |
---|
52 | ADD_ERROR_SIGNAL, |
---|
53 | ADD_PROMPT_SIGNAL, |
---|
54 | BLOCKLIST_SIGNAL, |
---|
55 | BUSY_SIGNAL, |
---|
56 | PORT_SIGNAL, |
---|
57 | PREFS_SIGNAL, |
---|
58 | |
---|
59 | LAST_SIGNAL |
---|
60 | }; |
---|
61 | |
---|
62 | static guint signals[LAST_SIGNAL] = { 0 }; |
---|
63 | |
---|
64 | static void core_maybe_inhibit_hibernation (TrCore * core); |
---|
65 | |
---|
66 | struct TrCorePrivate |
---|
67 | { |
---|
68 | GFileMonitor * monitor; |
---|
69 | gulong monitor_tag; |
---|
70 | GFile * monitor_dir; |
---|
71 | GSList * monitor_files; |
---|
72 | gulong monitor_idle_tag; |
---|
73 | |
---|
74 | gboolean adding_from_watch_dir; |
---|
75 | gboolean inhibit_allowed; |
---|
76 | gboolean have_inhibit_cookie; |
---|
77 | gboolean dbus_error; |
---|
78 | guint inhibit_cookie; |
---|
79 | gint busy_count; |
---|
80 | GtkTreeModel * raw_model; |
---|
81 | GtkTreeModel * sorted_model; |
---|
82 | tr_session * session; |
---|
83 | GStringChunk * string_chunk; |
---|
84 | }; |
---|
85 | |
---|
86 | static int |
---|
87 | core_is_disposed (const TrCore * core) |
---|
88 | { |
---|
89 | return !core || !core->priv->sorted_model; |
---|
90 | } |
---|
91 | |
---|
92 | G_DEFINE_TYPE (TrCore, tr_core, G_TYPE_OBJECT) |
---|
93 | |
---|
94 | static void |
---|
95 | core_dispose (GObject * o) |
---|
96 | { |
---|
97 | TrCore * core = TR_CORE (o); |
---|
98 | |
---|
99 | if (core->priv->sorted_model != NULL) |
---|
100 | { |
---|
101 | g_object_unref (core->priv->sorted_model); |
---|
102 | core->priv->sorted_model = NULL; |
---|
103 | core->priv->raw_model = NULL; |
---|
104 | } |
---|
105 | |
---|
106 | G_OBJECT_CLASS (tr_core_parent_class)->dispose (o); |
---|
107 | } |
---|
108 | |
---|
109 | static void |
---|
110 | core_finalize (GObject * o) |
---|
111 | { |
---|
112 | TrCore * core = TR_CORE (o); |
---|
113 | |
---|
114 | g_string_chunk_free (core->priv->string_chunk); |
---|
115 | |
---|
116 | G_OBJECT_CLASS (tr_core_parent_class)->finalize (o); |
---|
117 | } |
---|
118 | |
---|
119 | static void |
---|
120 | tr_core_class_init (TrCoreClass * core_class) |
---|
121 | { |
---|
122 | GObjectClass * gobject_class; |
---|
123 | GType core_type = G_TYPE_FROM_CLASS (core_class); |
---|
124 | |
---|
125 | g_type_class_add_private (core_class, sizeof (struct TrCorePrivate)); |
---|
126 | |
---|
127 | gobject_class = G_OBJECT_CLASS (core_class); |
---|
128 | gobject_class->dispose = core_dispose; |
---|
129 | gobject_class->finalize = core_finalize; |
---|
130 | |
---|
131 | signals[ADD_ERROR_SIGNAL] = |
---|
132 | g_signal_new ("add-error", core_type, |
---|
133 | G_SIGNAL_RUN_LAST, |
---|
134 | G_STRUCT_OFFSET (TrCoreClass, add_error), |
---|
135 | NULL, NULL, |
---|
136 | g_cclosure_marshal_VOID__UINT_POINTER, |
---|
137 | G_TYPE_NONE, |
---|
138 | 2, G_TYPE_UINT, G_TYPE_POINTER); |
---|
139 | |
---|
140 | signals[ADD_PROMPT_SIGNAL] = |
---|
141 | g_signal_new ("add-prompt", core_type, |
---|
142 | G_SIGNAL_RUN_LAST, |
---|
143 | G_STRUCT_OFFSET (TrCoreClass, add_prompt), |
---|
144 | NULL, NULL, |
---|
145 | g_cclosure_marshal_VOID__POINTER, |
---|
146 | G_TYPE_NONE, |
---|
147 | 1, G_TYPE_POINTER); |
---|
148 | |
---|
149 | signals[BUSY_SIGNAL] = |
---|
150 | g_signal_new ("busy", core_type, |
---|
151 | G_SIGNAL_RUN_FIRST, |
---|
152 | G_STRUCT_OFFSET (TrCoreClass, busy), |
---|
153 | NULL, NULL, |
---|
154 | g_cclosure_marshal_VOID__BOOLEAN, |
---|
155 | G_TYPE_NONE, |
---|
156 | 1, G_TYPE_BOOLEAN); |
---|
157 | |
---|
158 | signals[BLOCKLIST_SIGNAL] = |
---|
159 | g_signal_new ("blocklist-updated", core_type, |
---|
160 | G_SIGNAL_RUN_FIRST, |
---|
161 | G_STRUCT_OFFSET (TrCoreClass, blocklist_updated), |
---|
162 | NULL, NULL, |
---|
163 | g_cclosure_marshal_VOID__INT, |
---|
164 | G_TYPE_NONE, |
---|
165 | 1, G_TYPE_INT); |
---|
166 | |
---|
167 | signals[PORT_SIGNAL] = |
---|
168 | g_signal_new ("port-tested", core_type, |
---|
169 | G_SIGNAL_RUN_LAST, |
---|
170 | G_STRUCT_OFFSET (TrCoreClass, port_tested), |
---|
171 | NULL, NULL, |
---|
172 | g_cclosure_marshal_VOID__BOOLEAN, |
---|
173 | G_TYPE_NONE, |
---|
174 | 1, G_TYPE_BOOLEAN); |
---|
175 | |
---|
176 | signals[PREFS_SIGNAL] = |
---|
177 | g_signal_new ("prefs-changed", core_type, |
---|
178 | G_SIGNAL_RUN_LAST, |
---|
179 | G_STRUCT_OFFSET (TrCoreClass, prefs_changed), |
---|
180 | NULL, NULL, |
---|
181 | g_cclosure_marshal_VOID__INT, |
---|
182 | G_TYPE_NONE, |
---|
183 | 1, G_TYPE_INT); |
---|
184 | } |
---|
185 | |
---|
186 | static void |
---|
187 | tr_core_init (TrCore * core) |
---|
188 | { |
---|
189 | GtkListStore * store; |
---|
190 | struct TrCorePrivate * p; |
---|
191 | |
---|
192 | /* column types for the model used to store torrent information */ |
---|
193 | /* keep this in sync with the enum near the bottom of tr_core.h */ |
---|
194 | GType types[] = { G_TYPE_POINTER, /* collated name */ |
---|
195 | G_TYPE_POINTER, /* tr_torrent* */ |
---|
196 | G_TYPE_INT, /* torrent id */ |
---|
197 | G_TYPE_DOUBLE, /* tr_stat.pieceUploadSpeed_KBps */ |
---|
198 | G_TYPE_DOUBLE, /* tr_stat.pieceDownloadSpeed_KBps */ |
---|
199 | G_TYPE_DOUBLE, /* tr_stat.recheckProgress */ |
---|
200 | G_TYPE_BOOLEAN, /* filter.c:ACTIVITY_FILTER_ACTIVE */ |
---|
201 | G_TYPE_INT, /* tr_stat.activity */ |
---|
202 | G_TYPE_UCHAR, /* tr_stat.finished */ |
---|
203 | G_TYPE_CHAR, /* tr_priority_t */ |
---|
204 | G_TYPE_INT, /* tr_stat.queuePosition */ |
---|
205 | G_TYPE_UINT, /* build_torrent_trackers_hash () */ |
---|
206 | G_TYPE_INT, /* MC_ERROR */ |
---|
207 | G_TYPE_INT }; /* MC_ACTIVE_PEER_COUNT */ |
---|
208 | |
---|
209 | p = core->priv = G_TYPE_INSTANCE_GET_PRIVATE (core, |
---|
210 | TR_CORE_TYPE, |
---|
211 | struct TrCorePrivate); |
---|
212 | |
---|
213 | /* create the model used to store torrent data */ |
---|
214 | g_assert (G_N_ELEMENTS (types) == MC_ROW_COUNT); |
---|
215 | store = gtk_list_store_newv (MC_ROW_COUNT, types); |
---|
216 | |
---|
217 | p->raw_model = GTK_TREE_MODEL (store); |
---|
218 | p->sorted_model = gtk_tree_model_sort_new_with_model (p->raw_model); |
---|
219 | p->string_chunk = g_string_chunk_new (2048); |
---|
220 | g_object_unref (p->raw_model); |
---|
221 | } |
---|
222 | |
---|
223 | |
---|
224 | |
---|
225 | /*** |
---|
226 | **** EMIT SIGNALS |
---|
227 | ***/ |
---|
228 | |
---|
229 | static inline void |
---|
230 | core_emit_blocklist_udpated (TrCore * core, int ruleCount) |
---|
231 | { |
---|
232 | g_signal_emit (core, signals[BLOCKLIST_SIGNAL], 0, ruleCount); |
---|
233 | } |
---|
234 | |
---|
235 | static inline void |
---|
236 | core_emit_port_tested (TrCore * core, gboolean is_open) |
---|
237 | { |
---|
238 | g_signal_emit (core, signals[PORT_SIGNAL], 0, is_open); |
---|
239 | } |
---|
240 | |
---|
241 | static inline void |
---|
242 | core_emit_err (TrCore * core, enum tr_core_err type, const char * msg) |
---|
243 | { |
---|
244 | g_signal_emit (core, signals[ADD_ERROR_SIGNAL], 0, type, msg); |
---|
245 | } |
---|
246 | |
---|
247 | static inline void |
---|
248 | core_emit_busy (TrCore * core, gboolean is_busy) |
---|
249 | { |
---|
250 | g_signal_emit (core, signals[BUSY_SIGNAL], 0, is_busy); |
---|
251 | } |
---|
252 | |
---|
253 | void |
---|
254 | gtr_core_pref_changed (TrCore * core, const tr_quark key) |
---|
255 | { |
---|
256 | g_signal_emit (core, signals[PREFS_SIGNAL], 0, key); |
---|
257 | } |
---|
258 | |
---|
259 | /*** |
---|
260 | **** |
---|
261 | ***/ |
---|
262 | |
---|
263 | static GtkTreeModel * |
---|
264 | core_raw_model (TrCore * core) |
---|
265 | { |
---|
266 | return core_is_disposed (core) ? NULL : core->priv->raw_model; |
---|
267 | } |
---|
268 | |
---|
269 | GtkTreeModel * |
---|
270 | gtr_core_model (TrCore * core) |
---|
271 | { |
---|
272 | return core_is_disposed (core) ? NULL : core->priv->sorted_model; |
---|
273 | } |
---|
274 | |
---|
275 | tr_session * |
---|
276 | gtr_core_session (TrCore * core) |
---|
277 | { |
---|
278 | return core_is_disposed (core) ? NULL : core->priv->session; |
---|
279 | } |
---|
280 | |
---|
281 | /*** |
---|
282 | **** BUSY |
---|
283 | ***/ |
---|
284 | |
---|
285 | static bool |
---|
286 | core_is_busy (TrCore * core) |
---|
287 | { |
---|
288 | return core->priv->busy_count > 0; |
---|
289 | } |
---|
290 | |
---|
291 | static void |
---|
292 | core_add_to_busy (TrCore * core, int addMe) |
---|
293 | { |
---|
294 | const bool wasBusy = core_is_busy (core); |
---|
295 | |
---|
296 | core->priv->busy_count += addMe; |
---|
297 | |
---|
298 | if (wasBusy != core_is_busy (core)) |
---|
299 | core_emit_busy (core, core_is_busy (core)); |
---|
300 | } |
---|
301 | |
---|
302 | static void core_inc_busy (TrCore * core) { core_add_to_busy (core, 1); } |
---|
303 | static void core_dec_busy (TrCore * core) { core_add_to_busy (core, -1); } |
---|
304 | |
---|
305 | /*** |
---|
306 | **** |
---|
307 | **** SORTING THE MODEL |
---|
308 | **** |
---|
309 | ***/ |
---|
310 | |
---|
311 | static gboolean |
---|
312 | is_valid_eta (int t) |
---|
313 | { |
---|
314 | return (t != TR_ETA_NOT_AVAIL) && (t != TR_ETA_UNKNOWN); |
---|
315 | } |
---|
316 | |
---|
317 | static int |
---|
318 | compare_eta (int a, int b) |
---|
319 | { |
---|
320 | int ret; |
---|
321 | |
---|
322 | const gboolean a_valid = is_valid_eta (a); |
---|
323 | const gboolean b_valid = is_valid_eta (b); |
---|
324 | |
---|
325 | if (!a_valid && !b_valid) |
---|
326 | ret = 0; |
---|
327 | else if (!a_valid) |
---|
328 | ret = -1; |
---|
329 | else if (!b_valid) |
---|
330 | ret = 1; |
---|
331 | else |
---|
332 | ret = a < b ? 1 : -1; |
---|
333 | |
---|
334 | return ret; |
---|
335 | } |
---|
336 | |
---|
337 | static int |
---|
338 | compare_double (double a, double b) |
---|
339 | { |
---|
340 | int ret; |
---|
341 | |
---|
342 | if (a < b) |
---|
343 | ret = -1; |
---|
344 | else if (a > b) |
---|
345 | ret = 1; |
---|
346 | else |
---|
347 | ret = 0; |
---|
348 | |
---|
349 | return ret; |
---|
350 | } |
---|
351 | |
---|
352 | static int |
---|
353 | compare_uint64 (uint64_t a, uint64_t b) |
---|
354 | { |
---|
355 | int ret; |
---|
356 | |
---|
357 | if (a < b) |
---|
358 | ret = -1; |
---|
359 | else if (a > b) |
---|
360 | ret = 1; |
---|
361 | else |
---|
362 | ret = 0; |
---|
363 | |
---|
364 | return ret; |
---|
365 | } |
---|
366 | |
---|
367 | static int |
---|
368 | compare_int (int a, int b) |
---|
369 | { |
---|
370 | int ret; |
---|
371 | |
---|
372 | if (a < b) |
---|
373 | ret = -1; |
---|
374 | else if (a > b) |
---|
375 | ret = 1; |
---|
376 | else |
---|
377 | ret = 0; |
---|
378 | |
---|
379 | return ret; |
---|
380 | } |
---|
381 | |
---|
382 | static int |
---|
383 | compare_ratio (double a, double b) |
---|
384 | { |
---|
385 | int ret; |
---|
386 | |
---|
387 | if ((int)a == TR_RATIO_INF && (int)b == TR_RATIO_INF) |
---|
388 | ret = 0; |
---|
389 | else if ((int)a == TR_RATIO_INF) |
---|
390 | ret = 1; |
---|
391 | else if ((int)b == TR_RATIO_INF) |
---|
392 | ret = -1; |
---|
393 | else |
---|
394 | ret = compare_double (a, b); |
---|
395 | |
---|
396 | return ret; |
---|
397 | } |
---|
398 | |
---|
399 | static int |
---|
400 | compare_time (time_t a, time_t b) |
---|
401 | { |
---|
402 | int ret; |
---|
403 | |
---|
404 | if (a < b) |
---|
405 | ret = -1; |
---|
406 | else if (a > b) |
---|
407 | ret = 1; |
---|
408 | else |
---|
409 | ret = 0; |
---|
410 | |
---|
411 | return ret; |
---|
412 | } |
---|
413 | |
---|
414 | static int |
---|
415 | compare_by_name (GtkTreeModel * m, |
---|
416 | GtkTreeIter * a, |
---|
417 | GtkTreeIter * b, |
---|
418 | gpointer user_data UNUSED) |
---|
419 | { |
---|
420 | const char *ca, *cb; |
---|
421 | gtk_tree_model_get (m, a, MC_NAME_COLLATED, &ca, -1); |
---|
422 | gtk_tree_model_get (m, b, MC_NAME_COLLATED, &cb, -1); |
---|
423 | return tr_strcmp0 (ca, cb); |
---|
424 | } |
---|
425 | |
---|
426 | static int |
---|
427 | compare_by_queue (GtkTreeModel * m, |
---|
428 | GtkTreeIter * a, |
---|
429 | GtkTreeIter * b, |
---|
430 | gpointer user_data UNUSED) |
---|
431 | { |
---|
432 | tr_torrent *ta, *tb; |
---|
433 | const tr_stat *sa, *sb; |
---|
434 | |
---|
435 | gtk_tree_model_get (m, a, MC_TORRENT, &ta, -1); |
---|
436 | sa = tr_torrentStatCached (ta); |
---|
437 | gtk_tree_model_get (m, b, MC_TORRENT, &tb, -1); |
---|
438 | sb = tr_torrentStatCached (tb); |
---|
439 | |
---|
440 | return sb->queuePosition - sa->queuePosition; |
---|
441 | } |
---|
442 | |
---|
443 | static int |
---|
444 | compare_by_ratio (GtkTreeModel* m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data) |
---|
445 | { |
---|
446 | int ret = 0; |
---|
447 | tr_torrent *ta, *tb; |
---|
448 | const tr_stat *sa, *sb; |
---|
449 | |
---|
450 | gtk_tree_model_get (m, a, MC_TORRENT, &ta, -1); |
---|
451 | sa = tr_torrentStatCached (ta); |
---|
452 | gtk_tree_model_get (m, b, MC_TORRENT, &tb, -1); |
---|
453 | sb = tr_torrentStatCached (tb); |
---|
454 | |
---|
455 | if (!ret) |
---|
456 | ret = compare_ratio (sa->ratio, sb->ratio); |
---|
457 | if (!ret) |
---|
458 | ret = compare_by_queue (m, a, b, user_data); |
---|
459 | return ret; |
---|
460 | } |
---|
461 | |
---|
462 | static int |
---|
463 | compare_by_activity (GtkTreeModel * m, |
---|
464 | GtkTreeIter * a, |
---|
465 | GtkTreeIter * b, |
---|
466 | gpointer user_data) |
---|
467 | { |
---|
468 | int ret = 0; |
---|
469 | tr_torrent *ta, *tb; |
---|
470 | const tr_stat *sa, *sb; |
---|
471 | double aUp, aDown, bUp, bDown; |
---|
472 | |
---|
473 | gtk_tree_model_get (m, a, MC_SPEED_UP, &aUp, |
---|
474 | MC_SPEED_DOWN, &aDown, |
---|
475 | MC_TORRENT, &ta, |
---|
476 | -1); |
---|
477 | gtk_tree_model_get (m, b, MC_SPEED_UP, &bUp, |
---|
478 | MC_SPEED_DOWN, &bDown, |
---|
479 | MC_TORRENT, &tb, |
---|
480 | -1); |
---|
481 | sa = tr_torrentStatCached (ta); |
---|
482 | sb = tr_torrentStatCached (tb); |
---|
483 | |
---|
484 | if (!ret) |
---|
485 | ret = compare_double (aUp+aDown, bUp+bDown); |
---|
486 | if (!ret) |
---|
487 | ret = compare_uint64 (sa->uploadedEver, sb->uploadedEver); |
---|
488 | if (!ret) |
---|
489 | ret = compare_by_queue (m, a, b, user_data); |
---|
490 | |
---|
491 | return ret; |
---|
492 | } |
---|
493 | |
---|
494 | static int |
---|
495 | compare_by_age (GtkTreeModel * m, |
---|
496 | GtkTreeIter * a, |
---|
497 | GtkTreeIter * b, |
---|
498 | gpointer u) |
---|
499 | { |
---|
500 | int ret = 0; |
---|
501 | tr_torrent *ta, *tb; |
---|
502 | |
---|
503 | gtk_tree_model_get (m, a, MC_TORRENT, &ta, -1); |
---|
504 | gtk_tree_model_get (m, b, MC_TORRENT, &tb, -1); |
---|
505 | |
---|
506 | if (!ret) |
---|
507 | ret = compare_time (tr_torrentStatCached (ta)->addedDate, |
---|
508 | tr_torrentStatCached (tb)->addedDate); |
---|
509 | |
---|
510 | if (!ret) |
---|
511 | ret = compare_by_name (m, a, b, u); |
---|
512 | |
---|
513 | return ret; |
---|
514 | } |
---|
515 | |
---|
516 | static int |
---|
517 | compare_by_size (GtkTreeModel * m, |
---|
518 | GtkTreeIter * a, |
---|
519 | GtkTreeIter * b, |
---|
520 | gpointer u) |
---|
521 | { |
---|
522 | int ret = 0; |
---|
523 | tr_torrent *t; |
---|
524 | const tr_info *ia, *ib; |
---|
525 | |
---|
526 | gtk_tree_model_get (m, a, MC_TORRENT, &t, -1); |
---|
527 | ia = tr_torrentInfo (t); |
---|
528 | gtk_tree_model_get (m, b, MC_TORRENT, &t, -1); |
---|
529 | ib = tr_torrentInfo (t); |
---|
530 | |
---|
531 | if (!ret) |
---|
532 | ret = compare_uint64 (ia->totalSize, ib->totalSize); |
---|
533 | if (!ret) |
---|
534 | ret = compare_by_name (m, a, b, u); |
---|
535 | |
---|
536 | return ret; |
---|
537 | } |
---|
538 | |
---|
539 | static int |
---|
540 | compare_by_progress (GtkTreeModel * m, |
---|
541 | GtkTreeIter * a, |
---|
542 | GtkTreeIter * b, |
---|
543 | gpointer u) |
---|
544 | { |
---|
545 | int ret = 0; |
---|
546 | tr_torrent * t; |
---|
547 | const tr_stat *sa, *sb; |
---|
548 | |
---|
549 | gtk_tree_model_get (m, a, MC_TORRENT, &t, -1); |
---|
550 | sa = tr_torrentStatCached (t); |
---|
551 | gtk_tree_model_get (m, b, MC_TORRENT, &t, -1); |
---|
552 | sb = tr_torrentStatCached (t); |
---|
553 | |
---|
554 | if (!ret) |
---|
555 | ret = compare_double (sa->percentComplete, sb->percentComplete); |
---|
556 | if (!ret) |
---|
557 | ret = compare_double (sa->seedRatioPercentDone, sb->seedRatioPercentDone); |
---|
558 | if (!ret) |
---|
559 | ret = compare_by_ratio (m, a, b, u); |
---|
560 | |
---|
561 | return ret; |
---|
562 | } |
---|
563 | |
---|
564 | static int |
---|
565 | compare_by_eta (GtkTreeModel * m, |
---|
566 | GtkTreeIter * a, |
---|
567 | GtkTreeIter * b, |
---|
568 | gpointer u) |
---|
569 | { |
---|
570 | int ret = 0; |
---|
571 | tr_torrent *ta, *tb; |
---|
572 | |
---|
573 | gtk_tree_model_get (m, a, MC_TORRENT, &ta, -1); |
---|
574 | gtk_tree_model_get (m, b, MC_TORRENT, &tb, -1); |
---|
575 | |
---|
576 | if (!ret) |
---|
577 | ret = compare_eta (tr_torrentStatCached (ta)->eta, |
---|
578 | tr_torrentStatCached (tb)->eta); |
---|
579 | |
---|
580 | if (!ret) |
---|
581 | ret = compare_by_name (m, a, b, u); |
---|
582 | |
---|
583 | return ret; |
---|
584 | } |
---|
585 | |
---|
586 | static int |
---|
587 | compare_by_state (GtkTreeModel * m, |
---|
588 | GtkTreeIter * a, |
---|
589 | GtkTreeIter * b, |
---|
590 | gpointer u) |
---|
591 | { |
---|
592 | int ret = 0; |
---|
593 | int sa, sb; |
---|
594 | tr_torrent *ta, *tb; |
---|
595 | |
---|
596 | gtk_tree_model_get (m, a, MC_ACTIVITY, &sa, MC_TORRENT, &ta, -1); |
---|
597 | gtk_tree_model_get (m, b, MC_ACTIVITY, &sb, MC_TORRENT, &tb, -1); |
---|
598 | |
---|
599 | if (!ret) |
---|
600 | ret = compare_int (sa, sb); |
---|
601 | if (!ret) |
---|
602 | ret = compare_by_queue (m, a, b, u); |
---|
603 | |
---|
604 | return ret; |
---|
605 | } |
---|
606 | |
---|
607 | static void |
---|
608 | core_set_sort_mode (TrCore * core, const char * mode, gboolean is_reversed) |
---|
609 | { |
---|
610 | const int col = MC_TORRENT; |
---|
611 | GtkTreeIterCompareFunc sort_func; |
---|
612 | GtkSortType type = is_reversed ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING; |
---|
613 | GtkTreeSortable * sortable = GTK_TREE_SORTABLE (gtr_core_model (core)); |
---|
614 | |
---|
615 | if (!strcmp (mode, "sort-by-activity")) |
---|
616 | sort_func = compare_by_activity; |
---|
617 | else if (!strcmp (mode, "sort-by-age")) |
---|
618 | sort_func = compare_by_age; |
---|
619 | else if (!strcmp (mode, "sort-by-progress")) |
---|
620 | sort_func = compare_by_progress; |
---|
621 | else if (!strcmp (mode, "sort-by-queue")) |
---|
622 | sort_func = compare_by_queue; |
---|
623 | else if (!strcmp (mode, "sort-by-time-left")) |
---|
624 | sort_func = compare_by_eta; |
---|
625 | else if (!strcmp (mode, "sort-by-ratio")) |
---|
626 | sort_func = compare_by_ratio; |
---|
627 | else if (!strcmp (mode, "sort-by-state")) |
---|
628 | sort_func = compare_by_state; |
---|
629 | else if (!strcmp (mode, "sort-by-size")) |
---|
630 | sort_func = compare_by_size; |
---|
631 | else { |
---|
632 | sort_func = compare_by_name; |
---|
633 | type = is_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING; |
---|
634 | } |
---|
635 | |
---|
636 | gtk_tree_sortable_set_sort_func (sortable, col, sort_func, NULL, NULL); |
---|
637 | gtk_tree_sortable_set_sort_column_id (sortable, col, type); |
---|
638 | } |
---|
639 | |
---|
640 | /*** |
---|
641 | **** |
---|
642 | **** WATCHDIR |
---|
643 | **** |
---|
644 | ***/ |
---|
645 | |
---|
646 | static time_t |
---|
647 | get_file_mtime (GFile * file) |
---|
648 | { |
---|
649 | time_t mtime; |
---|
650 | GFileInfo * info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL); |
---|
651 | mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); |
---|
652 | g_object_unref (G_OBJECT (info)); |
---|
653 | return mtime; |
---|
654 | } |
---|
655 | |
---|
656 | static void |
---|
657 | rename_torrent_and_unref_file (GFile * file) |
---|
658 | { |
---|
659 | GError * error = NULL; |
---|
660 | GFileInfo * info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, 0, NULL, NULL); |
---|
661 | const char * old_name = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME); |
---|
662 | char * new_name = g_strdup_printf ("%s.added", old_name); |
---|
663 | GFile * new_file = g_file_set_display_name (file, new_name, NULL, &error); |
---|
664 | if (error != NULL) |
---|
665 | g_message ("Unable to rename \"%s\" as \"%s\": %s", old_name, new_name, error->message); |
---|
666 | if (new_file != NULL) |
---|
667 | g_object_unref (G_OBJECT (new_file)); |
---|
668 | g_free (new_name); |
---|
669 | g_object_unref (G_OBJECT (info)); |
---|
670 | g_object_unref (G_OBJECT (file)); |
---|
671 | g_clear_error (&error); |
---|
672 | } |
---|
673 | |
---|
674 | static gboolean |
---|
675 | core_watchdir_idle (gpointer gcore) |
---|
676 | { |
---|
677 | GSList * l; |
---|
678 | GSList * changing = NULL; |
---|
679 | GSList * unchanging = NULL; |
---|
680 | TrCore * core = TR_CORE (gcore); |
---|
681 | const time_t now = tr_time (); |
---|
682 | struct TrCorePrivate * p = core->priv; |
---|
683 | |
---|
684 | /* separate the files into two lists: changing and unchanging */ |
---|
685 | for (l=p->monitor_files; l!=NULL; l=l->next) |
---|
686 | { |
---|
687 | GFile * file = l->data; |
---|
688 | const time_t mtime = get_file_mtime (file); |
---|
689 | if (mtime + 2 >= now) |
---|
690 | changing = g_slist_prepend (changing, file); |
---|
691 | else |
---|
692 | unchanging = g_slist_prepend (unchanging, file); |
---|
693 | } |
---|
694 | |
---|
695 | /* add the files that have stopped changing */ |
---|
696 | if (unchanging != NULL) |
---|
697 | { |
---|
698 | const gboolean do_start = gtr_pref_flag_get (TR_KEY_start_added_torrents); |
---|
699 | const gboolean do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window); |
---|
700 | |
---|
701 | core->priv->adding_from_watch_dir = TRUE; |
---|
702 | gtr_core_add_files (core, unchanging, do_start, do_prompt, TRUE); |
---|
703 | g_slist_foreach (unchanging, (GFunc)rename_torrent_and_unref_file, NULL); |
---|
704 | g_slist_free (unchanging); |
---|
705 | core->priv->adding_from_watch_dir = FALSE; |
---|
706 | } |
---|
707 | |
---|
708 | /* keep monitoring the ones that are still changing */ |
---|
709 | g_slist_free (p->monitor_files); |
---|
710 | p->monitor_files = changing; |
---|
711 | |
---|
712 | /* if monitor_files is nonempty, keep checking every second */ |
---|
713 | if (core->priv->monitor_files) |
---|
714 | return G_SOURCE_CONTINUE; |
---|
715 | core->priv->monitor_idle_tag = 0; |
---|
716 | return G_SOURCE_REMOVE; |
---|
717 | } |
---|
718 | |
---|
719 | /* If this file is a torrent, add it to our list */ |
---|
720 | static void |
---|
721 | core_watchdir_monitor_file (TrCore * core, GFile * file) |
---|
722 | { |
---|
723 | char * filename = g_file_get_path (file); |
---|
724 | const gboolean is_torrent = g_str_has_suffix (filename, ".torrent"); |
---|
725 | |
---|
726 | if (is_torrent) |
---|
727 | { |
---|
728 | GSList * l; |
---|
729 | struct TrCorePrivate * p = core->priv; |
---|
730 | |
---|
731 | /* if we're not already watching this file, start watching it now */ |
---|
732 | for (l=p->monitor_files; l!=NULL; l=l->next) |
---|
733 | if (g_file_equal (file, l->data)) |
---|
734 | break; |
---|
735 | |
---|
736 | if (l == NULL) |
---|
737 | { |
---|
738 | g_object_ref (file); |
---|
739 | p->monitor_files = g_slist_prepend (p->monitor_files, file); |
---|
740 | if (p->monitor_idle_tag == 0) |
---|
741 | p->monitor_idle_tag = gdk_threads_add_timeout_seconds (1, core_watchdir_idle, core); |
---|
742 | } |
---|
743 | } |
---|
744 | |
---|
745 | g_free (filename); |
---|
746 | } |
---|
747 | |
---|
748 | /* GFileMonitor noticed a file was created */ |
---|
749 | static void |
---|
750 | on_file_changed_in_watchdir (GFileMonitor * monitor UNUSED, |
---|
751 | GFile * file, |
---|
752 | GFile * other_type UNUSED, |
---|
753 | GFileMonitorEvent event_type, |
---|
754 | gpointer core) |
---|
755 | { |
---|
756 | if (event_type == G_FILE_MONITOR_EVENT_CREATED) |
---|
757 | core_watchdir_monitor_file (core, file); |
---|
758 | } |
---|
759 | |
---|
760 | /* walk through the pre-existing files in the watchdir */ |
---|
761 | static void |
---|
762 | core_watchdir_scan (TrCore * core) |
---|
763 | { |
---|
764 | const char * dirname = gtr_pref_string_get (TR_KEY_watch_dir); |
---|
765 | GDir * dir = g_dir_open (dirname, 0, NULL); |
---|
766 | |
---|
767 | if (dir != NULL) |
---|
768 | { |
---|
769 | const char * name; |
---|
770 | while ((name = g_dir_read_name (dir))) |
---|
771 | { |
---|
772 | char * filename = g_build_filename (dirname, name, NULL); |
---|
773 | GFile * file = g_file_new_for_path (filename); |
---|
774 | core_watchdir_monitor_file (core, file); |
---|
775 | g_object_unref (file); |
---|
776 | g_free (filename); |
---|
777 | } |
---|
778 | |
---|
779 | g_dir_close (dir); |
---|
780 | } |
---|
781 | } |
---|
782 | |
---|
783 | static void |
---|
784 | core_watchdir_update (TrCore * core) |
---|
785 | { |
---|
786 | const gboolean is_enabled = gtr_pref_flag_get (TR_KEY_watch_dir_enabled); |
---|
787 | GFile * dir = g_file_new_for_path (gtr_pref_string_get (TR_KEY_watch_dir)); |
---|
788 | struct TrCorePrivate * p = core->priv; |
---|
789 | |
---|
790 | if (p->monitor && (!is_enabled || !g_file_equal (dir, p->monitor_dir))) |
---|
791 | { |
---|
792 | g_signal_handler_disconnect (p->monitor, p->monitor_tag); |
---|
793 | g_file_monitor_cancel (p->monitor); |
---|
794 | g_object_unref (p->monitor); |
---|
795 | g_object_unref (p->monitor_dir); |
---|
796 | |
---|
797 | p->monitor_dir = NULL; |
---|
798 | p->monitor = NULL; |
---|
799 | p->monitor_tag = 0; |
---|
800 | } |
---|
801 | |
---|
802 | if (is_enabled && !p->monitor) |
---|
803 | { |
---|
804 | GFileMonitor * m = g_file_monitor_directory (dir, 0, NULL, NULL); |
---|
805 | core_watchdir_scan (core); |
---|
806 | |
---|
807 | g_object_ref (dir); |
---|
808 | p->monitor = m; |
---|
809 | p->monitor_dir = dir; |
---|
810 | p->monitor_tag = g_signal_connect (m, "changed", |
---|
811 | G_CALLBACK (on_file_changed_in_watchdir), core); |
---|
812 | } |
---|
813 | |
---|
814 | g_object_unref (dir); |
---|
815 | } |
---|
816 | |
---|
817 | /*** |
---|
818 | **** |
---|
819 | ***/ |
---|
820 | |
---|
821 | static void |
---|
822 | on_pref_changed (TrCore * core, const tr_quark key, gpointer data UNUSED) |
---|
823 | { |
---|
824 | switch (key) |
---|
825 | { |
---|
826 | case TR_KEY_sort_mode: |
---|
827 | case TR_KEY_sort_reversed: |
---|
828 | { |
---|
829 | const char * mode = gtr_pref_string_get (TR_KEY_sort_mode); |
---|
830 | const gboolean is_reversed = gtr_pref_flag_get (TR_KEY_sort_reversed); |
---|
831 | core_set_sort_mode (core, mode, is_reversed); |
---|
832 | break; |
---|
833 | } |
---|
834 | |
---|
835 | case TR_KEY_peer_limit_global: |
---|
836 | tr_sessionSetPeerLimit (gtr_core_session (core), gtr_pref_int_get (key)); |
---|
837 | break; |
---|
838 | |
---|
839 | case TR_KEY_peer_limit_per_torrent: |
---|
840 | tr_sessionSetPeerLimitPerTorrent (gtr_core_session (core), gtr_pref_int_get (key)); |
---|
841 | break; |
---|
842 | |
---|
843 | case TR_KEY_inhibit_desktop_hibernation: |
---|
844 | core_maybe_inhibit_hibernation (core); |
---|
845 | break; |
---|
846 | |
---|
847 | case TR_KEY_watch_dir: |
---|
848 | case TR_KEY_watch_dir_enabled: |
---|
849 | core_watchdir_update (core); |
---|
850 | break; |
---|
851 | |
---|
852 | default: |
---|
853 | break; |
---|
854 | } |
---|
855 | } |
---|
856 | |
---|
857 | /** |
---|
858 | *** |
---|
859 | **/ |
---|
860 | |
---|
861 | TrCore * |
---|
862 | gtr_core_new (tr_session * session) |
---|
863 | { |
---|
864 | TrCore * core = TR_CORE (g_object_new (TR_CORE_TYPE, NULL)); |
---|
865 | |
---|
866 | core->priv->session = session; |
---|
867 | |
---|
868 | /* init from prefs & listen to pref changes */ |
---|
869 | on_pref_changed (core, TR_KEY_sort_mode, NULL); |
---|
870 | on_pref_changed (core, TR_KEY_sort_reversed, NULL); |
---|
871 | on_pref_changed (core, TR_KEY_watch_dir_enabled, NULL); |
---|
872 | on_pref_changed (core, TR_KEY_peer_limit_global, NULL); |
---|
873 | on_pref_changed (core, TR_KEY_inhibit_desktop_hibernation, NULL); |
---|
874 | g_signal_connect (core, "prefs-changed", G_CALLBACK (on_pref_changed), NULL); |
---|
875 | |
---|
876 | return core; |
---|
877 | } |
---|
878 | |
---|
879 | tr_session * |
---|
880 | gtr_core_close (TrCore * core) |
---|
881 | { |
---|
882 | tr_session * session = gtr_core_session (core); |
---|
883 | |
---|
884 | if (session) |
---|
885 | { |
---|
886 | core->priv->session = NULL; |
---|
887 | gtr_pref_save (session); |
---|
888 | } |
---|
889 | |
---|
890 | return session; |
---|
891 | } |
---|
892 | |
---|
893 | /*** |
---|
894 | **** COMPLETENESS CALLBACK |
---|
895 | ***/ |
---|
896 | |
---|
897 | struct notify_callback_data |
---|
898 | { |
---|
899 | TrCore * core; |
---|
900 | int torrent_id; |
---|
901 | }; |
---|
902 | |
---|
903 | static gboolean |
---|
904 | on_torrent_completeness_changed_idle (gpointer gdata) |
---|
905 | { |
---|
906 | struct notify_callback_data * data = gdata; |
---|
907 | gtr_notify_torrent_completed (data->core, data->torrent_id); |
---|
908 | g_object_unref (G_OBJECT (data->core)); |
---|
909 | g_free (data); |
---|
910 | return FALSE; |
---|
911 | } |
---|
912 | |
---|
913 | /* this is called in the libtransmission thread, *NOT* the GTK+ thread, |
---|
914 | so delegate to the GTK+ thread before calling notify's dbus code... */ |
---|
915 | static void |
---|
916 | on_torrent_completeness_changed (tr_torrent * tor, |
---|
917 | tr_completeness completeness, |
---|
918 | bool was_running, |
---|
919 | void * gcore) |
---|
920 | { |
---|
921 | if (was_running && (completeness != TR_LEECH) && (tr_torrentStat (tor)->sizeWhenDone != 0)) |
---|
922 | { |
---|
923 | struct notify_callback_data * data = g_new (struct notify_callback_data, 1); |
---|
924 | data->core = gcore; |
---|
925 | data->torrent_id = tr_torrentId (tor); |
---|
926 | g_object_ref (G_OBJECT (data->core)); |
---|
927 | gdk_threads_add_idle (on_torrent_completeness_changed_idle, data); |
---|
928 | } |
---|
929 | } |
---|
930 | |
---|
931 | /*** |
---|
932 | **** METADATA CALLBACK |
---|
933 | ***/ |
---|
934 | |
---|
935 | static const char* |
---|
936 | get_collated_name (TrCore * core, const tr_torrent * tor) |
---|
937 | { |
---|
938 | char buf[2048]; |
---|
939 | const char * name = tr_torrentName (tor); |
---|
940 | char * down = g_utf8_strdown (name ? name : "", -1); |
---|
941 | const tr_info * inf = tr_torrentInfo (tor); |
---|
942 | g_snprintf (buf, sizeof (buf), "%s\t%s", down, inf->hashString); |
---|
943 | g_free (down); |
---|
944 | return g_string_chunk_insert_const (core->priv->string_chunk, buf); |
---|
945 | } |
---|
946 | |
---|
947 | struct metadata_callback_data |
---|
948 | { |
---|
949 | TrCore * core; |
---|
950 | int torrent_id; |
---|
951 | }; |
---|
952 | |
---|
953 | static gboolean |
---|
954 | find_row_from_torrent_id (GtkTreeModel * model, int id, GtkTreeIter * setme) |
---|
955 | { |
---|
956 | GtkTreeIter iter; |
---|
957 | gboolean match = FALSE; |
---|
958 | |
---|
959 | if (gtk_tree_model_iter_children (model, &iter, NULL)) do |
---|
960 | { |
---|
961 | int row_id; |
---|
962 | gtk_tree_model_get (model, &iter, MC_TORRENT_ID, &row_id, -1); |
---|
963 | match = id == row_id; |
---|
964 | } |
---|
965 | while (!match && gtk_tree_model_iter_next (model, &iter)); |
---|
966 | |
---|
967 | if (match) |
---|
968 | *setme = iter; |
---|
969 | |
---|
970 | return match; |
---|
971 | } |
---|
972 | |
---|
973 | static gboolean |
---|
974 | on_torrent_metadata_changed_idle (gpointer gdata) |
---|
975 | { |
---|
976 | struct notify_callback_data * data = gdata; |
---|
977 | tr_session * session = gtr_core_session (data->core); |
---|
978 | tr_torrent * tor = tr_torrentFindFromId (session, data->torrent_id); |
---|
979 | |
---|
980 | /* update the torrent's collated name */ |
---|
981 | if (tor != NULL) |
---|
982 | { |
---|
983 | GtkTreeIter iter; |
---|
984 | GtkTreeModel * model = core_raw_model (data->core); |
---|
985 | if (find_row_from_torrent_id (model, data->torrent_id, &iter)) |
---|
986 | { |
---|
987 | const char * collated = get_collated_name (data->core, tor); |
---|
988 | GtkListStore * store = GTK_LIST_STORE (model); |
---|
989 | gtk_list_store_set (store, &iter, MC_NAME_COLLATED, collated, -1); |
---|
990 | } |
---|
991 | } |
---|
992 | |
---|
993 | /* cleanup */ |
---|
994 | g_object_unref (G_OBJECT (data->core)); |
---|
995 | g_free (data); |
---|
996 | return FALSE; |
---|
997 | } |
---|
998 | |
---|
999 | /* this is called in the libtransmission thread, *NOT* the GTK+ thread, |
---|
1000 | so delegate to the GTK+ thread before changing our list store... */ |
---|
1001 | static void |
---|
1002 | on_torrent_metadata_changed (tr_torrent * tor, void * gcore) |
---|
1003 | { |
---|
1004 | struct notify_callback_data * data = g_new (struct notify_callback_data, 1); |
---|
1005 | data->core = gcore; |
---|
1006 | data->torrent_id = tr_torrentId (tor); |
---|
1007 | g_object_ref (G_OBJECT (data->core)); |
---|
1008 | gdk_threads_add_idle (on_torrent_metadata_changed_idle, data); |
---|
1009 | } |
---|
1010 | |
---|
1011 | /*** |
---|
1012 | **** |
---|
1013 | **** ADDING TORRENTS |
---|
1014 | **** |
---|
1015 | ***/ |
---|
1016 | |
---|
1017 | static unsigned int |
---|
1018 | build_torrent_trackers_hash (tr_torrent * tor) |
---|
1019 | { |
---|
1020 | unsigned int i; |
---|
1021 | const char * pch; |
---|
1022 | uint64_t hash = 0; |
---|
1023 | const tr_info * const inf = tr_torrentInfo (tor); |
---|
1024 | |
---|
1025 | for (i=0; i<inf->trackerCount; ++i) |
---|
1026 | for (pch=inf->trackers[i].announce; *pch; ++pch) |
---|
1027 | hash = (hash<<4) ^ (hash>>28) ^ *pch; |
---|
1028 | |
---|
1029 | return hash; |
---|
1030 | } |
---|
1031 | |
---|
1032 | static gboolean |
---|
1033 | is_torrent_active (const tr_stat * st) |
---|
1034 | { |
---|
1035 | return (st->peersSendingToUs > 0) |
---|
1036 | || (st->peersGettingFromUs > 0) |
---|
1037 | || (st->activity == TR_STATUS_CHECK); |
---|
1038 | } |
---|
1039 | |
---|
1040 | void |
---|
1041 | gtr_core_add_torrent (TrCore * core, tr_torrent * tor, gboolean do_notify) |
---|
1042 | { |
---|
1043 | if (tor != NULL) |
---|
1044 | { |
---|
1045 | GtkTreeIter unused; |
---|
1046 | const tr_stat * st = tr_torrentStat (tor); |
---|
1047 | const char * collated = get_collated_name (core, tor); |
---|
1048 | const unsigned int trackers_hash = build_torrent_trackers_hash (tor); |
---|
1049 | GtkListStore * store = GTK_LIST_STORE (core_raw_model (core)); |
---|
1050 | |
---|
1051 | gtk_list_store_insert_with_values (store, &unused, 0, |
---|
1052 | MC_NAME_COLLATED, collated, |
---|
1053 | MC_TORRENT, tor, |
---|
1054 | MC_TORRENT_ID, tr_torrentId (tor), |
---|
1055 | MC_SPEED_UP, st->pieceUploadSpeed_KBps, |
---|
1056 | MC_SPEED_DOWN, st->pieceDownloadSpeed_KBps, |
---|
1057 | MC_RECHECK_PROGRESS, st->recheckProgress, |
---|
1058 | MC_ACTIVE, is_torrent_active (st), |
---|
1059 | MC_ACTIVITY, st->activity, |
---|
1060 | MC_FINISHED, st->finished, |
---|
1061 | MC_PRIORITY, tr_torrentGetPriority (tor), |
---|
1062 | MC_QUEUE_POSITION, st->queuePosition, |
---|
1063 | MC_TRACKERS, trackers_hash, |
---|
1064 | -1); |
---|
1065 | |
---|
1066 | if (do_notify) |
---|
1067 | gtr_notify_torrent_added (tr_torrentName (tor)); |
---|
1068 | |
---|
1069 | tr_torrentSetMetadataCallback (tor, on_torrent_metadata_changed, core); |
---|
1070 | tr_torrentSetCompletenessCallback (tor, on_torrent_completeness_changed, core); |
---|
1071 | } |
---|
1072 | } |
---|
1073 | |
---|
1074 | static tr_torrent * |
---|
1075 | core_create_new_torrent (TrCore * core, tr_ctor * ctor) |
---|
1076 | { |
---|
1077 | int errcode = 0; |
---|
1078 | tr_torrent * tor; |
---|
1079 | bool do_trash = false; |
---|
1080 | tr_session * session = gtr_core_session (core); |
---|
1081 | |
---|
1082 | /* let the gtk client handle the removal, since libT |
---|
1083 | * doesn't have any concept of the glib trash API */ |
---|
1084 | tr_ctorGetDeleteSource (ctor, &do_trash); |
---|
1085 | tr_ctorSetDeleteSource (ctor, FALSE); |
---|
1086 | tor = tr_torrentNew (ctor, &errcode); |
---|
1087 | |
---|
1088 | if (tor && do_trash) |
---|
1089 | { |
---|
1090 | const char * config = tr_sessionGetConfigDir (session); |
---|
1091 | const char * source = tr_ctorGetSourceFile (ctor); |
---|
1092 | |
---|
1093 | if (source != NULL) |
---|
1094 | { |
---|
1095 | /* #1294: don't delete the .torrent file if it's our internal copy */ |
---|
1096 | const int is_internal = (strstr (source, config) == source); |
---|
1097 | if (!is_internal) |
---|
1098 | gtr_file_trash_or_remove (source); |
---|
1099 | } |
---|
1100 | } |
---|
1101 | |
---|
1102 | return tor; |
---|
1103 | } |
---|
1104 | |
---|
1105 | static int |
---|
1106 | core_add_ctor (TrCore * core, tr_ctor * ctor, |
---|
1107 | gboolean do_prompt, gboolean do_notify) |
---|
1108 | { |
---|
1109 | tr_info inf; |
---|
1110 | int err = tr_torrentParse (ctor, &inf); |
---|
1111 | |
---|
1112 | switch (err) |
---|
1113 | { |
---|
1114 | case TR_PARSE_ERR: |
---|
1115 | break; |
---|
1116 | |
---|
1117 | case TR_PARSE_DUPLICATE: |
---|
1118 | /* don't complain about .torrent files in the watch directory |
---|
1119 | * that have already been added... that gets annoying and we |
---|
1120 | * don't want to be nagging users to clean up their watch dirs */ |
---|
1121 | if (!tr_ctorGetSourceFile (ctor) || !core->priv->adding_from_watch_dir) |
---|
1122 | core_emit_err (core, err, inf.name); |
---|
1123 | tr_metainfoFree (&inf); |
---|
1124 | tr_ctorFree (ctor); |
---|
1125 | break; |
---|
1126 | |
---|
1127 | default: |
---|
1128 | if (do_prompt) |
---|
1129 | { |
---|
1130 | g_signal_emit (core, signals[ADD_PROMPT_SIGNAL], 0, ctor); |
---|
1131 | } |
---|
1132 | else |
---|
1133 | { |
---|
1134 | gtr_core_add_torrent (core, core_create_new_torrent (core, ctor), do_notify); |
---|
1135 | tr_ctorFree (ctor); |
---|
1136 | } |
---|
1137 | tr_metainfoFree (&inf); |
---|
1138 | break; |
---|
1139 | } |
---|
1140 | |
---|
1141 | return err; |
---|
1142 | } |
---|
1143 | |
---|
1144 | static void |
---|
1145 | core_apply_defaults (tr_ctor * ctor) |
---|
1146 | { |
---|
1147 | if (tr_ctorGetPaused (ctor, TR_FORCE, NULL)) |
---|
1148 | tr_ctorSetPaused (ctor, TR_FORCE, !gtr_pref_flag_get (TR_KEY_start_added_torrents)); |
---|
1149 | |
---|
1150 | if (tr_ctorGetDeleteSource (ctor, NULL)) |
---|
1151 | tr_ctorSetDeleteSource (ctor, gtr_pref_flag_get (TR_KEY_trash_original_torrent_files)); |
---|
1152 | |
---|
1153 | if (tr_ctorGetPeerLimit (ctor, TR_FORCE, NULL)) |
---|
1154 | tr_ctorSetPeerLimit (ctor, TR_FORCE, gtr_pref_int_get (TR_KEY_peer_limit_per_torrent)); |
---|
1155 | |
---|
1156 | if (tr_ctorGetDownloadDir (ctor, TR_FORCE, NULL)) |
---|
1157 | tr_ctorSetDownloadDir (ctor, TR_FORCE, gtr_pref_string_get (TR_KEY_download_dir)); |
---|
1158 | } |
---|
1159 | |
---|
1160 | void |
---|
1161 | gtr_core_add_ctor (TrCore * core, tr_ctor * ctor) |
---|
1162 | { |
---|
1163 | const gboolean do_notify = FALSE; |
---|
1164 | const gboolean do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window); |
---|
1165 | core_apply_defaults (ctor); |
---|
1166 | core_add_ctor (core, ctor, do_prompt, do_notify); |
---|
1167 | } |
---|
1168 | |
---|
1169 | /*** |
---|
1170 | **** |
---|
1171 | ***/ |
---|
1172 | |
---|
1173 | struct add_from_url_data |
---|
1174 | { |
---|
1175 | TrCore * core; |
---|
1176 | tr_ctor * ctor; |
---|
1177 | bool do_prompt; |
---|
1178 | bool do_notify; |
---|
1179 | }; |
---|
1180 | |
---|
1181 | static void |
---|
1182 | add_file_async_callback (GObject * file, GAsyncResult * result, gpointer gdata) |
---|
1183 | { |
---|
1184 | gsize length; |
---|
1185 | char * contents; |
---|
1186 | GError * error = NULL; |
---|
1187 | struct add_from_url_data * data = gdata; |
---|
1188 | |
---|
1189 | if (!g_file_load_contents_finish (G_FILE (file), result, &contents, &length, NULL, &error)) |
---|
1190 | { |
---|
1191 | g_message (_("Couldn't read \"%s\": %s"), g_file_get_parse_name (G_FILE (file)), error->message); |
---|
1192 | g_error_free (error); |
---|
1193 | } |
---|
1194 | else if (!tr_ctorSetMetainfo (data->ctor, (const uint8_t*)contents, length)) |
---|
1195 | { |
---|
1196 | core_add_ctor (data->core, data->ctor, data->do_prompt, data->do_notify); |
---|
1197 | } |
---|
1198 | else |
---|
1199 | { |
---|
1200 | tr_ctorFree (data->ctor); |
---|
1201 | } |
---|
1202 | |
---|
1203 | core_dec_busy (data->core); |
---|
1204 | g_free (data); |
---|
1205 | } |
---|
1206 | |
---|
1207 | static bool |
---|
1208 | add_file (TrCore * core, |
---|
1209 | GFile * file, |
---|
1210 | gboolean do_start, |
---|
1211 | gboolean do_prompt, |
---|
1212 | gboolean do_notify) |
---|
1213 | { |
---|
1214 | bool handled = false; |
---|
1215 | tr_session * session = gtr_core_session (core); |
---|
1216 | |
---|
1217 | if (session != NULL) |
---|
1218 | { |
---|
1219 | tr_ctor * ctor; |
---|
1220 | bool tried = false; |
---|
1221 | bool loaded = false; |
---|
1222 | |
---|
1223 | ctor = tr_ctorNew (session); |
---|
1224 | core_apply_defaults (ctor); |
---|
1225 | tr_ctorSetPaused (ctor, TR_FORCE, !do_start); |
---|
1226 | |
---|
1227 | /* local files... */ |
---|
1228 | if (!tried) |
---|
1229 | { |
---|
1230 | char * str = g_file_get_path (file); |
---|
1231 | if ((tried = g_file_test (str, G_FILE_TEST_EXISTS))) |
---|
1232 | loaded = !tr_ctorSetMetainfoFromFile (ctor, str); |
---|
1233 | g_free (str); |
---|
1234 | } |
---|
1235 | |
---|
1236 | /* magnet links... */ |
---|
1237 | if (!tried && g_file_has_uri_scheme (file, "magnet")) |
---|
1238 | { |
---|
1239 | /* GFile mangles the original string with /// so we have to un-mangle */ |
---|
1240 | char * str = g_file_get_parse_name (file); |
---|
1241 | char * magnet = g_strdup_printf ("magnet:%s", strchr (str, '?')); |
---|
1242 | tried = true; |
---|
1243 | loaded = !tr_ctorSetMetainfoFromMagnetLink (ctor, magnet); |
---|
1244 | g_free (magnet); |
---|
1245 | g_free (str); |
---|
1246 | } |
---|
1247 | |
---|
1248 | /* hashcodes that we can turn into magnet links... */ |
---|
1249 | if (!tried) |
---|
1250 | { |
---|
1251 | char * str = g_file_get_basename (file); |
---|
1252 | if (gtr_is_hex_hashcode (str)) |
---|
1253 | { |
---|
1254 | char * magnet = g_strdup_printf ("magnet:?xt=urn:btih:%s", str); |
---|
1255 | tried = true; |
---|
1256 | loaded = !tr_ctorSetMetainfoFromMagnetLink (ctor, magnet); |
---|
1257 | g_free (magnet); |
---|
1258 | } |
---|
1259 | g_free (str); |
---|
1260 | } |
---|
1261 | |
---|
1262 | /* if we were able to load the metainfo, add the torrent */ |
---|
1263 | if (loaded) |
---|
1264 | { |
---|
1265 | handled = true; |
---|
1266 | core_add_ctor (core, ctor, do_prompt, do_notify); |
---|
1267 | } |
---|
1268 | else if (g_file_has_uri_scheme (file, "http") || |
---|
1269 | g_file_has_uri_scheme (file, "https") || |
---|
1270 | g_file_has_uri_scheme (file, "ftp")) |
---|
1271 | { |
---|
1272 | struct add_from_url_data * data; |
---|
1273 | |
---|
1274 | data = g_new0 (struct add_from_url_data, 1); |
---|
1275 | data->core = core; |
---|
1276 | data->ctor = ctor; |
---|
1277 | data->do_prompt = do_prompt; |
---|
1278 | data->do_notify = do_notify; |
---|
1279 | |
---|
1280 | handled = true; |
---|
1281 | core_inc_busy (core); |
---|
1282 | g_file_load_contents_async (file, NULL, add_file_async_callback, data); |
---|
1283 | } |
---|
1284 | else |
---|
1285 | { |
---|
1286 | tr_ctorFree (ctor); |
---|
1287 | g_message (_("Skipping unknown torrent \"%s\""), g_file_get_parse_name (file)); |
---|
1288 | } |
---|
1289 | } |
---|
1290 | |
---|
1291 | return handled; |
---|
1292 | } |
---|
1293 | |
---|
1294 | bool |
---|
1295 | gtr_core_add_from_url (TrCore * core, const char * uri) |
---|
1296 | { |
---|
1297 | bool handled; |
---|
1298 | const bool do_start = gtr_pref_flag_get (TR_KEY_start_added_torrents); |
---|
1299 | const bool do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window); |
---|
1300 | const bool do_notify = false; |
---|
1301 | |
---|
1302 | GFile * file = g_file_new_for_uri (uri); |
---|
1303 | handled = add_file (core, file, do_start, do_prompt, do_notify); |
---|
1304 | g_object_unref (file); |
---|
1305 | gtr_core_torrents_added (core); |
---|
1306 | |
---|
1307 | return handled; |
---|
1308 | } |
---|
1309 | |
---|
1310 | void |
---|
1311 | gtr_core_add_files (TrCore * core, |
---|
1312 | GSList * files, |
---|
1313 | gboolean do_start, |
---|
1314 | gboolean do_prompt, |
---|
1315 | gboolean do_notify) |
---|
1316 | { |
---|
1317 | GSList * l; |
---|
1318 | |
---|
1319 | for (l=files; l!=NULL; l=l->next) |
---|
1320 | add_file (core, l->data, do_start, do_prompt, do_notify); |
---|
1321 | |
---|
1322 | gtr_core_torrents_added (core); |
---|
1323 | } |
---|
1324 | |
---|
1325 | void |
---|
1326 | gtr_core_torrents_added (TrCore * self) |
---|
1327 | { |
---|
1328 | gtr_core_update (self); |
---|
1329 | core_emit_err (self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL); |
---|
1330 | } |
---|
1331 | |
---|
1332 | void |
---|
1333 | gtr_core_remove_torrent (TrCore * core, int id, gboolean delete_local_data) |
---|
1334 | { |
---|
1335 | tr_torrent * tor = gtr_core_find_torrent (core, id); |
---|
1336 | |
---|
1337 | if (tor != NULL) |
---|
1338 | { |
---|
1339 | /* remove from the gui */ |
---|
1340 | GtkTreeIter iter; |
---|
1341 | GtkTreeModel * model = core_raw_model (core); |
---|
1342 | if (find_row_from_torrent_id (model, id, &iter)) |
---|
1343 | gtk_list_store_remove (GTK_LIST_STORE (model), &iter); |
---|
1344 | |
---|
1345 | /* remove the torrent */ |
---|
1346 | tr_torrentRemove (tor, delete_local_data, gtr_file_trash_or_remove); |
---|
1347 | } |
---|
1348 | } |
---|
1349 | |
---|
1350 | void |
---|
1351 | gtr_core_load (TrCore * self, gboolean forcePaused) |
---|
1352 | { |
---|
1353 | int i; |
---|
1354 | tr_ctor * ctor; |
---|
1355 | tr_torrent ** torrents; |
---|
1356 | int count = 0; |
---|
1357 | |
---|
1358 | ctor = tr_ctorNew (gtr_core_session (self)); |
---|
1359 | if (forcePaused) |
---|
1360 | tr_ctorSetPaused (ctor, TR_FORCE, TRUE); |
---|
1361 | tr_ctorSetPeerLimit (ctor, TR_FALLBACK, |
---|
1362 | gtr_pref_int_get (TR_KEY_peer_limit_per_torrent)); |
---|
1363 | |
---|
1364 | torrents = tr_sessionLoadTorrents (gtr_core_session (self), ctor, &count); |
---|
1365 | for (i=0; i<count; ++i) |
---|
1366 | gtr_core_add_torrent (self, torrents[i], FALSE); |
---|
1367 | |
---|
1368 | tr_free (torrents); |
---|
1369 | tr_ctorFree (ctor); |
---|
1370 | } |
---|
1371 | |
---|
1372 | void |
---|
1373 | gtr_core_clear (TrCore * self) |
---|
1374 | { |
---|
1375 | gtk_list_store_clear (GTK_LIST_STORE (core_raw_model (self))); |
---|
1376 | } |
---|
1377 | |
---|
1378 | /*** |
---|
1379 | **** |
---|
1380 | ***/ |
---|
1381 | |
---|
1382 | static int |
---|
1383 | gtr_compare_double (const double a, const double b, int decimal_places) |
---|
1384 | { |
---|
1385 | int ret; |
---|
1386 | const int64_t ia = (int64_t)(a * pow (10, decimal_places)); |
---|
1387 | const int64_t ib = (int64_t)(b * pow (10, decimal_places)); |
---|
1388 | |
---|
1389 | if (ia < ib) |
---|
1390 | ret = -1; |
---|
1391 | else if (ia > ib) |
---|
1392 | ret = 1; |
---|
1393 | else |
---|
1394 | ret = 0; |
---|
1395 | |
---|
1396 | return ret; |
---|
1397 | } |
---|
1398 | |
---|
1399 | static void |
---|
1400 | update_foreach (GtkTreeModel * model, GtkTreeIter * iter) |
---|
1401 | { |
---|
1402 | int oldActivity, newActivity; |
---|
1403 | int oldActivePeerCount, newActivePeerCount; |
---|
1404 | int oldError, newError; |
---|
1405 | bool oldFinished, newFinished; |
---|
1406 | int oldQueuePosition, newQueuePosition; |
---|
1407 | tr_priority_t oldPriority, newPriority; |
---|
1408 | unsigned int oldTrackers, newTrackers; |
---|
1409 | double oldUpSpeed, newUpSpeed; |
---|
1410 | double oldDownSpeed, newDownSpeed; |
---|
1411 | double oldRecheckProgress, newRecheckProgress; |
---|
1412 | gboolean oldActive, newActive; |
---|
1413 | const tr_stat * st; |
---|
1414 | tr_torrent * tor; |
---|
1415 | |
---|
1416 | /* get the old states */ |
---|
1417 | gtk_tree_model_get (model, iter, |
---|
1418 | MC_TORRENT, &tor, |
---|
1419 | MC_ACTIVE, &oldActive, |
---|
1420 | MC_ACTIVE_PEER_COUNT, &oldActivePeerCount, |
---|
1421 | MC_ERROR, &oldError, |
---|
1422 | MC_ACTIVITY, &oldActivity, |
---|
1423 | MC_FINISHED, &oldFinished, |
---|
1424 | MC_PRIORITY, &oldPriority, |
---|
1425 | MC_QUEUE_POSITION, &oldQueuePosition, |
---|
1426 | MC_TRACKERS, &oldTrackers, |
---|
1427 | MC_SPEED_UP, &oldUpSpeed, |
---|
1428 | MC_RECHECK_PROGRESS, &oldRecheckProgress, |
---|
1429 | MC_SPEED_DOWN, &oldDownSpeed, |
---|
1430 | -1); |
---|
1431 | |
---|
1432 | /* get the new states */ |
---|
1433 | st = tr_torrentStat (tor); |
---|
1434 | newActive = is_torrent_active (st); |
---|
1435 | newActivity = st->activity; |
---|
1436 | newFinished = st->finished; |
---|
1437 | newPriority = tr_torrentGetPriority (tor); |
---|
1438 | newQueuePosition = st->queuePosition; |
---|
1439 | newTrackers = build_torrent_trackers_hash (tor); |
---|
1440 | newUpSpeed = st->pieceUploadSpeed_KBps; |
---|
1441 | newDownSpeed = st->pieceDownloadSpeed_KBps; |
---|
1442 | newRecheckProgress = st->recheckProgress; |
---|
1443 | newActivePeerCount = st->peersSendingToUs + st->peersGettingFromUs + st->webseedsSendingToUs; |
---|
1444 | newError = st->error; |
---|
1445 | |
---|
1446 | /* updating the model triggers off resort/refresh, |
---|
1447 | so don't do it unless something's actually changed... */ |
---|
1448 | if ((newActive != oldActive) |
---|
1449 | || (newActivity != oldActivity) |
---|
1450 | || (newFinished != oldFinished) |
---|
1451 | || (newPriority != oldPriority) |
---|
1452 | || (newQueuePosition != oldQueuePosition) |
---|
1453 | || (newError != oldError) |
---|
1454 | || (newActivePeerCount != oldActivePeerCount) |
---|
1455 | || (newTrackers != oldTrackers) |
---|
1456 | || gtr_compare_double (newUpSpeed, oldUpSpeed, 2) |
---|
1457 | || gtr_compare_double (newDownSpeed, oldDownSpeed, 2) |
---|
1458 | || gtr_compare_double (newRecheckProgress, oldRecheckProgress, 2)) |
---|
1459 | { |
---|
1460 | gtk_list_store_set (GTK_LIST_STORE (model), iter, |
---|
1461 | MC_ACTIVE, newActive, |
---|
1462 | MC_ACTIVE_PEER_COUNT, newActivePeerCount, |
---|
1463 | MC_ERROR, newError, |
---|
1464 | MC_ACTIVITY, newActivity, |
---|
1465 | MC_FINISHED, newFinished, |
---|
1466 | MC_PRIORITY, newPriority, |
---|
1467 | MC_QUEUE_POSITION, newQueuePosition, |
---|
1468 | MC_TRACKERS, newTrackers, |
---|
1469 | MC_SPEED_UP, newUpSpeed, |
---|
1470 | MC_SPEED_DOWN, newDownSpeed, |
---|
1471 | MC_RECHECK_PROGRESS, newRecheckProgress, |
---|
1472 | -1); |
---|
1473 | } |
---|
1474 | } |
---|
1475 | |
---|
1476 | void |
---|
1477 | gtr_core_update (TrCore * core) |
---|
1478 | { |
---|
1479 | GtkTreeIter iter; |
---|
1480 | GtkTreeModel * model; |
---|
1481 | |
---|
1482 | /* update the model */ |
---|
1483 | model = core_raw_model (core); |
---|
1484 | if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do |
---|
1485 | update_foreach (model, &iter); |
---|
1486 | while (gtk_tree_model_iter_next (model, &iter)); |
---|
1487 | |
---|
1488 | /* update hibernation */ |
---|
1489 | core_maybe_inhibit_hibernation (core); |
---|
1490 | } |
---|
1491 | |
---|
1492 | /** |
---|
1493 | *** Hibernate |
---|
1494 | **/ |
---|
1495 | |
---|
1496 | #define SESSION_MANAGER_SERVICE_NAME "org.gnome.SessionManager" |
---|
1497 | #define SESSION_MANAGER_INTERFACE "org.gnome.SessionManager" |
---|
1498 | #define SESSION_MANAGER_OBJECT_PATH "/org/gnome/SessionManager" |
---|
1499 | |
---|
1500 | static gboolean |
---|
1501 | gtr_inhibit_hibernation (guint * cookie) |
---|
1502 | { |
---|
1503 | gboolean success; |
---|
1504 | GVariant * response; |
---|
1505 | GDBusConnection * connection; |
---|
1506 | GError * err = NULL; |
---|
1507 | const char * application = "Transmission BitTorrent Client"; |
---|
1508 | const char * reason = "BitTorrent Activity"; |
---|
1509 | const int toplevel_xid = 0; |
---|
1510 | const int flags = 4; /* Inhibit suspending the session or computer */ |
---|
1511 | |
---|
1512 | connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &err); |
---|
1513 | |
---|
1514 | response = g_dbus_connection_call_sync (connection, |
---|
1515 | SESSION_MANAGER_SERVICE_NAME, |
---|
1516 | SESSION_MANAGER_OBJECT_PATH, |
---|
1517 | SESSION_MANAGER_INTERFACE, |
---|
1518 | "Inhibit", |
---|
1519 | g_variant_new ("(susu)", application, toplevel_xid, reason, flags), |
---|
1520 | NULL, G_DBUS_CALL_FLAGS_NONE, |
---|
1521 | 1000, NULL, &err); |
---|
1522 | |
---|
1523 | if (response != NULL) |
---|
1524 | *cookie = g_variant_get_uint32 (g_variant_get_child_value (response, 0)); |
---|
1525 | |
---|
1526 | success = (response != NULL) && (err == NULL); |
---|
1527 | |
---|
1528 | /* logging */ |
---|
1529 | if (success) |
---|
1530 | { |
---|
1531 | tr_inf ("%s", _("Inhibiting desktop hibernation")); |
---|
1532 | } |
---|
1533 | else |
---|
1534 | { |
---|
1535 | tr_err (_("Couldn't inhibit desktop hibernation: %s"), err->message); |
---|
1536 | g_error_free (err); |
---|
1537 | } |
---|
1538 | |
---|
1539 | /* cleanup */ |
---|
1540 | if (response != NULL) |
---|
1541 | g_variant_unref (response); |
---|
1542 | if (connection != NULL) |
---|
1543 | g_object_unref (connection); |
---|
1544 | |
---|
1545 | return success; |
---|
1546 | } |
---|
1547 | |
---|
1548 | static void |
---|
1549 | gtr_uninhibit_hibernation (guint inhibit_cookie) |
---|
1550 | { |
---|
1551 | GVariant * response; |
---|
1552 | GDBusConnection * connection; |
---|
1553 | GError * err = NULL; |
---|
1554 | |
---|
1555 | connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &err); |
---|
1556 | |
---|
1557 | response = g_dbus_connection_call_sync (connection, |
---|
1558 | SESSION_MANAGER_SERVICE_NAME, |
---|
1559 | SESSION_MANAGER_OBJECT_PATH, |
---|
1560 | SESSION_MANAGER_INTERFACE, |
---|
1561 | "Uninhibit", |
---|
1562 | g_variant_new ("(u)", inhibit_cookie), |
---|
1563 | NULL, G_DBUS_CALL_FLAGS_NONE, |
---|
1564 | 1000, NULL, &err); |
---|
1565 | |
---|
1566 | /* logging */ |
---|
1567 | if (err == NULL) |
---|
1568 | { |
---|
1569 | tr_inf ("%s", _("Allowing desktop hibernation")); |
---|
1570 | } |
---|
1571 | else |
---|
1572 | { |
---|
1573 | g_warning ("Couldn't uninhibit desktop hibernation: %s.", err->message); |
---|
1574 | g_error_free (err); |
---|
1575 | } |
---|
1576 | |
---|
1577 | /* cleanup */ |
---|
1578 | g_variant_unref (response); |
---|
1579 | g_object_unref (connection); |
---|
1580 | } |
---|
1581 | |
---|
1582 | static void |
---|
1583 | gtr_core_set_hibernation_allowed (TrCore * core, gboolean allowed) |
---|
1584 | { |
---|
1585 | g_return_if_fail (core); |
---|
1586 | g_return_if_fail (core->priv); |
---|
1587 | |
---|
1588 | core->priv->inhibit_allowed = allowed != 0; |
---|
1589 | |
---|
1590 | if (allowed && core->priv->have_inhibit_cookie) |
---|
1591 | { |
---|
1592 | gtr_uninhibit_hibernation (core->priv->inhibit_cookie); |
---|
1593 | core->priv->have_inhibit_cookie = FALSE; |
---|
1594 | } |
---|
1595 | |
---|
1596 | if (!allowed && !core->priv->have_inhibit_cookie |
---|
1597 | && !core->priv->dbus_error) |
---|
1598 | { |
---|
1599 | if (gtr_inhibit_hibernation (&core->priv->inhibit_cookie)) |
---|
1600 | core->priv->have_inhibit_cookie = TRUE; |
---|
1601 | else |
---|
1602 | core->priv->dbus_error = TRUE; |
---|
1603 | } |
---|
1604 | } |
---|
1605 | |
---|
1606 | static void |
---|
1607 | core_maybe_inhibit_hibernation (TrCore * core) |
---|
1608 | { |
---|
1609 | /* hibernation is allowed if EITHER |
---|
1610 | * (a) the "inhibit" pref is turned off OR |
---|
1611 | * (b) there aren't any active torrents */ |
---|
1612 | const gboolean hibernation_allowed = !gtr_pref_flag_get (TR_KEY_inhibit_desktop_hibernation) |
---|
1613 | || !gtr_core_get_active_torrent_count (core); |
---|
1614 | gtr_core_set_hibernation_allowed (core, hibernation_allowed); |
---|
1615 | } |
---|
1616 | |
---|
1617 | /** |
---|
1618 | *** Prefs |
---|
1619 | **/ |
---|
1620 | |
---|
1621 | static void |
---|
1622 | core_commit_prefs_change (TrCore * core, const tr_quark key) |
---|
1623 | { |
---|
1624 | gtr_core_pref_changed (core, key); |
---|
1625 | gtr_pref_save (gtr_core_session (core)); |
---|
1626 | } |
---|
1627 | |
---|
1628 | void |
---|
1629 | gtr_core_set_pref (TrCore * self, const tr_quark key, const char * newval) |
---|
1630 | { |
---|
1631 | if (tr_strcmp0 (newval, gtr_pref_string_get (key))) |
---|
1632 | { |
---|
1633 | gtr_pref_string_set (key, newval); |
---|
1634 | core_commit_prefs_change (self, key); |
---|
1635 | } |
---|
1636 | } |
---|
1637 | |
---|
1638 | void |
---|
1639 | gtr_core_set_pref_bool (TrCore * self, const tr_quark key, gboolean newval) |
---|
1640 | { |
---|
1641 | if (newval != gtr_pref_flag_get (key)) |
---|
1642 | { |
---|
1643 | gtr_pref_flag_set (key, newval); |
---|
1644 | core_commit_prefs_change (self, key); |
---|
1645 | } |
---|
1646 | } |
---|
1647 | |
---|
1648 | void |
---|
1649 | gtr_core_set_pref_int (TrCore * self, const tr_quark key, int newval) |
---|
1650 | { |
---|
1651 | if (newval != gtr_pref_int_get (key)) |
---|
1652 | { |
---|
1653 | gtr_pref_int_set (key, newval); |
---|
1654 | core_commit_prefs_change (self, key); |
---|
1655 | } |
---|
1656 | } |
---|
1657 | |
---|
1658 | void |
---|
1659 | gtr_core_set_pref_double (TrCore * self, const tr_quark key, double newval) |
---|
1660 | { |
---|
1661 | if (gtr_compare_double (newval, gtr_pref_double_get (key), 4)) |
---|
1662 | { |
---|
1663 | gtr_pref_double_set (key, newval); |
---|
1664 | core_commit_prefs_change (self, key); |
---|
1665 | } |
---|
1666 | } |
---|
1667 | |
---|
1668 | /*** |
---|
1669 | **** |
---|
1670 | **** RPC Interface |
---|
1671 | **** |
---|
1672 | ***/ |
---|
1673 | |
---|
1674 | /* #define DEBUG_RPC */ |
---|
1675 | |
---|
1676 | static int nextTag = 1; |
---|
1677 | |
---|
1678 | typedef void (server_response_func)(TrCore * core, tr_variant * response, gpointer user_data); |
---|
1679 | |
---|
1680 | struct pending_request_data |
---|
1681 | { |
---|
1682 | TrCore * core; |
---|
1683 | server_response_func * response_func; |
---|
1684 | gpointer response_func_user_data; |
---|
1685 | }; |
---|
1686 | |
---|
1687 | static GHashTable * pendingRequests = NULL; |
---|
1688 | |
---|
1689 | static gboolean |
---|
1690 | core_read_rpc_response_idle (void * vresponse) |
---|
1691 | { |
---|
1692 | tr_variant top; |
---|
1693 | int64_t intVal; |
---|
1694 | struct evbuffer * response = vresponse; |
---|
1695 | |
---|
1696 | tr_variantFromJson (&top, evbuffer_pullup (response, -1), evbuffer_get_length (response)); |
---|
1697 | |
---|
1698 | if (tr_variantDictFindInt (&top, TR_KEY_tag, &intVal)) |
---|
1699 | { |
---|
1700 | const int tag = (int)intVal; |
---|
1701 | struct pending_request_data * data = g_hash_table_lookup (pendingRequests, &tag); |
---|
1702 | if (data) |
---|
1703 | { |
---|
1704 | if (data->response_func) |
---|
1705 | (*data->response_func)(data->core, &top, data->response_func_user_data); |
---|
1706 | g_hash_table_remove (pendingRequests, &tag); |
---|
1707 | } |
---|
1708 | } |
---|
1709 | |
---|
1710 | tr_variantFree (&top); |
---|
1711 | evbuffer_free (response); |
---|
1712 | return FALSE; |
---|
1713 | } |
---|
1714 | |
---|
1715 | static void |
---|
1716 | core_read_rpc_response (tr_session * session UNUSED, |
---|
1717 | struct evbuffer * response, |
---|
1718 | void * unused UNUSED) |
---|
1719 | { |
---|
1720 | struct evbuffer * buf = evbuffer_new (); |
---|
1721 | evbuffer_add_buffer (buf, response); |
---|
1722 | gdk_threads_add_idle (core_read_rpc_response_idle, buf); |
---|
1723 | } |
---|
1724 | |
---|
1725 | static void |
---|
1726 | core_send_rpc_request (TrCore * core, const char * json, int tag, |
---|
1727 | server_response_func * response_func, |
---|
1728 | void * response_func_user_data) |
---|
1729 | { |
---|
1730 | tr_session * session = gtr_core_session (core); |
---|
1731 | |
---|
1732 | if (pendingRequests == NULL) |
---|
1733 | { |
---|
1734 | pendingRequests = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, g_free); |
---|
1735 | } |
---|
1736 | |
---|
1737 | if (session == NULL) |
---|
1738 | { |
---|
1739 | g_error ("GTK+ client doesn't support connections to remote servers yet."); |
---|
1740 | } |
---|
1741 | else |
---|
1742 | { |
---|
1743 | /* remember this request */ |
---|
1744 | struct pending_request_data * data; |
---|
1745 | data = g_new0 (struct pending_request_data, 1); |
---|
1746 | data->core = core; |
---|
1747 | data->response_func = response_func; |
---|
1748 | data->response_func_user_data = response_func_user_data; |
---|
1749 | g_hash_table_insert (pendingRequests, g_memdup (&tag, sizeof (int)), data); |
---|
1750 | |
---|
1751 | /* make the request */ |
---|
1752 | #ifdef DEBUG_RPC |
---|
1753 | g_message ("request: [%s]", json); |
---|
1754 | #endif |
---|
1755 | tr_rpc_request_exec_json (session, json, strlen (json), core_read_rpc_response, GINT_TO_POINTER (tag)); |
---|
1756 | } |
---|
1757 | } |
---|
1758 | |
---|
1759 | /*** |
---|
1760 | **** Sending a test-port request via RPC |
---|
1761 | ***/ |
---|
1762 | |
---|
1763 | static void |
---|
1764 | on_port_test_response (TrCore * core, tr_variant * response, gpointer u UNUSED) |
---|
1765 | { |
---|
1766 | tr_variant * args; |
---|
1767 | bool is_open = FALSE; |
---|
1768 | |
---|
1769 | if (tr_variantDictFindDict (response, TR_KEY_arguments, &args)) |
---|
1770 | tr_variantDictFindBool (args, TR_KEY_port_is_open, &is_open); |
---|
1771 | |
---|
1772 | core_emit_port_tested (core, is_open); |
---|
1773 | } |
---|
1774 | |
---|
1775 | void |
---|
1776 | gtr_core_port_test (TrCore * core) |
---|
1777 | { |
---|
1778 | char buf[64]; |
---|
1779 | const int tag = nextTag++; |
---|
1780 | g_snprintf (buf, sizeof (buf), "{ \"method\": \"port-test\", \"tag\": %d }", tag); |
---|
1781 | core_send_rpc_request (core, buf, tag, on_port_test_response, NULL); |
---|
1782 | } |
---|
1783 | |
---|
1784 | /*** |
---|
1785 | **** Updating a blocklist via RPC |
---|
1786 | ***/ |
---|
1787 | |
---|
1788 | static void |
---|
1789 | on_blocklist_response (TrCore * core, tr_variant * response, gpointer data UNUSED) |
---|
1790 | { |
---|
1791 | tr_variant * args; |
---|
1792 | int64_t ruleCount = -1; |
---|
1793 | |
---|
1794 | if (tr_variantDictFindDict (response, TR_KEY_arguments, &args)) |
---|
1795 | tr_variantDictFindInt (args, TR_KEY_blocklist_size, &ruleCount); |
---|
1796 | |
---|
1797 | if (ruleCount > 0) |
---|
1798 | gtr_pref_int_set (TR_KEY_blocklist_date, tr_time ()); |
---|
1799 | |
---|
1800 | core_emit_blocklist_udpated (core, ruleCount); |
---|
1801 | } |
---|
1802 | |
---|
1803 | void |
---|
1804 | gtr_core_blocklist_update (TrCore * core) |
---|
1805 | { |
---|
1806 | char buf[64]; |
---|
1807 | const int tag = nextTag++; |
---|
1808 | g_snprintf (buf, sizeof (buf), "{ \"method\": \"blocklist-update\", \"tag\": %d }", tag); |
---|
1809 | core_send_rpc_request (core, buf, tag, on_blocklist_response, NULL); |
---|
1810 | } |
---|
1811 | |
---|
1812 | /*** |
---|
1813 | **** |
---|
1814 | ***/ |
---|
1815 | |
---|
1816 | void |
---|
1817 | gtr_core_exec_json (TrCore * core, const char * json) |
---|
1818 | { |
---|
1819 | const int tag = nextTag++; |
---|
1820 | core_send_rpc_request (core, json, tag, NULL, NULL); |
---|
1821 | } |
---|
1822 | |
---|
1823 | void |
---|
1824 | gtr_core_exec (TrCore * core, const tr_variant * top) |
---|
1825 | { |
---|
1826 | char * json = tr_variantToStr (top, TR_VARIANT_FMT_JSON_LEAN, NULL); |
---|
1827 | gtr_core_exec_json (core, json); |
---|
1828 | tr_free (json); |
---|
1829 | } |
---|
1830 | |
---|
1831 | /*** |
---|
1832 | **** |
---|
1833 | ***/ |
---|
1834 | |
---|
1835 | size_t |
---|
1836 | gtr_core_get_torrent_count (TrCore * core) |
---|
1837 | { |
---|
1838 | return gtk_tree_model_iter_n_children (core_raw_model (core), NULL); |
---|
1839 | } |
---|
1840 | |
---|
1841 | size_t |
---|
1842 | gtr_core_get_active_torrent_count (TrCore * core) |
---|
1843 | { |
---|
1844 | GtkTreeIter iter; |
---|
1845 | size_t activeCount = 0; |
---|
1846 | GtkTreeModel * model = core_raw_model (core); |
---|
1847 | |
---|
1848 | if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do |
---|
1849 | { |
---|
1850 | int activity; |
---|
1851 | gtk_tree_model_get (model, &iter, MC_ACTIVITY, &activity, -1); |
---|
1852 | |
---|
1853 | if (activity != TR_STATUS_STOPPED) |
---|
1854 | ++activeCount; |
---|
1855 | } |
---|
1856 | while (gtk_tree_model_iter_next (model, &iter)); |
---|
1857 | |
---|
1858 | return activeCount; |
---|
1859 | } |
---|
1860 | |
---|
1861 | tr_torrent * |
---|
1862 | gtr_core_find_torrent (TrCore * core, int id) |
---|
1863 | { |
---|
1864 | tr_session * session; |
---|
1865 | tr_torrent * tor = NULL; |
---|
1866 | |
---|
1867 | if ((session = gtr_core_session (core))) |
---|
1868 | tor = tr_torrentFindFromId (session, id); |
---|
1869 | |
---|
1870 | return tor; |
---|
1871 | } |
---|
1872 | |
---|
1873 | void |
---|
1874 | gtr_core_open_folder (TrCore * core, int torrent_id) |
---|
1875 | { |
---|
1876 | const tr_torrent * tor = gtr_core_find_torrent (core, torrent_id); |
---|
1877 | |
---|
1878 | if (tor != NULL) |
---|
1879 | { |
---|
1880 | const gboolean single = tr_torrentInfo (tor)->fileCount == 1; |
---|
1881 | const char * currentDir = tr_torrentGetCurrentDir (tor); |
---|
1882 | |
---|
1883 | if (single) |
---|
1884 | { |
---|
1885 | gtr_open_file (currentDir); |
---|
1886 | } |
---|
1887 | else |
---|
1888 | { |
---|
1889 | char * path = g_build_filename (currentDir, tr_torrentName (tor), NULL); |
---|
1890 | gtr_open_file (path); |
---|
1891 | g_free (path); |
---|
1892 | } |
---|
1893 | } |
---|
1894 | } |
---|