1 /*
2 ** 1998-09-29 - This is a Russion space shuttle. It is also the module responsible for
3 ** reporting various commands' progress to the user, and providing a nice
4 ** way of aborting commands prematurely.
5 ** 1999-01-30 - Now cancels operation if ESCape is hit. Pretty convenient.
6 ** 1999-03-05 - Adapted for new selection management. Also fixes a couple of minor bugs.
7 ** 2000-07-16 - Operation can now be cancelled by closing the progress window.
8 */
9
10 #include "gentoo.h"
11
12 #include <sys/stat.h>
13 #include <unistd.h>
14
15 #include <gdk/gdkkeysyms.h>
16
17 #include "dirpane.h"
18 #include "fileutil.h"
19 #include "guiutil.h"
20 #include "miscutil.h"
21 #include "sizeutil.h"
22
23 #include "progress.h"
24
25 /* ----------------------------------------------------------------------------------------- */
26
27 static struct {
28 guint level; /* Current begin/end nesting level. Keep first!! */
29 guint32 flags; /* Flags from original progress_begin() call. */
30 GCancellable *cancel; /* This is from the future. */
31 gboolean show_byte; /* Show total bytes progress bar? */
32 gboolean show_item; /* Show the item bar? */
33 gboolean delayed; /* Set to 1 during initial display delay. */
34 guint tot_num; /* Total number of items to process. */
35 guint tot_pos; /* Index of current item. */
36 guint64 byte_tot; /* Total bytes in operation. */
37 guint64 byte_pos; /* Position, in bytes. */
38 gchar name[4 * FILENAME_MAX]; /* Current filename. */
39 off_t item_size; /* Size of current item being processed. */
40 off_t item_pos; /* Position in the item. */
41 GTimeVal time_begin; /* The time when pgs_progress_begin() was called. */
42 guint delay_time; /* Delay until display, in microseconds (!). */
43 guint last_secs; /* Elapsed seconds last time we displayed ETA and stuff. */
44
45 MainInfo *min;
46 GtkWidget *dlg;
47 GtkWidget *body;
48 GtkWidget *tot_pgs;
49
50 GtkWidget *byte_pgs;
51 GtkWidget *byte_high;
52
53 GtkWidget *item;
54 GtkWidget *item_pgs;
55
56 GtkWidget *eta; /* Label for elapsed time, speed, and ETA. */
57 } pgs_info = { 0U };
58
59 /* ----------------------------------------------------------------------------------------- */
60
61 /* 1998-09-30 - Count number of top-level selections.
62 ** 1998-10-01 - Added size support.
63 */
count_toplevel_selected(MainInfo * min,guint64 * size)64 static guint count_toplevel_selected(MainInfo *min, guint64 *size)
65 {
66 DirPane *dp = min->gui->cur_pane;
67 GSList *slist, *iter;
68 guint num = 0;
69 guint64 dummy;
70
71 if(size == NULL)
72 size = &dummy;
73
74 for(iter = slist = dp_get_selection(dp), *size = 0; iter != NULL; iter = g_slist_next(iter), num++)
75 *size += dp_row_get_size(dp_get_tree_model(dp), iter->data);
76 dp_free_selection(slist);
77
78 return num;
79 }
80
81 /* 1998-09-30 - Count number of items recursively from all selections.
82 ** 1998-10-01 - Added size support.
83 ** 1998-12-20 - Now uses the fut_dir_size() function for the recursive scanning.
84 */
count_recursive_selected(MainInfo * min,guint64 * size)85 static guint count_recursive_selected(MainInfo *min, guint64 *size)
86 {
87 DirPane *dp = min->gui->cur_pane;
88 GSList *slist, *iter;
89 guint num = 0;
90
91 if(size != NULL)
92 *size = 0U;
93 for(iter = slist = dp_get_selection(dp); iter != NULL; iter = g_slist_next(iter), num++)
94 {
95 if(size)
96 *size += dp_row_get_size(dp_get_tree_model(dp), iter->data);
97 if(dp_row_get_file_type(dp_get_tree_model(dp), iter->data, TRUE) == G_FILE_TYPE_DIRECTORY)
98 {
99 FUCount fu;
100 GFile *dir;
101
102 dir = dp_get_file_from_row(dp, iter->data);
103 if(fut_size_gfile(min, dir, size, &fu, NULL))
104 num += fu.num_total;
105 g_object_unref(dir);
106 }
107 }
108 dp_free_selection(slist);
109
110 return num;
111 }
112
evt_delete(GtkWidget * wid,GdkEvent * evt,gpointer user)113 static gint evt_delete(GtkWidget *wid, GdkEvent *evt, gpointer user)
114 {
115 g_cancellable_cancel(user);
116 return TRUE;
117 }
118
119 /* 1998-09-30 - A callback for the big "Cancel" button. */
evt_cancel_clicked(GtkWidget * wid,gpointer user)120 static gint evt_cancel_clicked(GtkWidget *wid, gpointer user)
121 {
122 g_cancellable_cancel(user);
123 return TRUE;
124 }
125
evt_keypress(GtkWidget * wid,GdkEventKey * evt,gpointer user)126 static gint evt_keypress(GtkWidget *wid, GdkEventKey *evt, gpointer user)
127 {
128 if(evt->keyval == GDK_KEY_Escape)
129 g_cancellable_cancel(user);
130 return TRUE;
131 }
132
133 /* 1998-09-30 - Begin a new "session" with progress reporting. */
pgs_progress_begin(MainInfo * min,const gchar * op_name,guint32 flags)134 void pgs_progress_begin(MainInfo *min, const gchar *op_name, guint32 flags)
135 {
136 gchar size_buf[32], buf[128];
137 GtkWidget *cancel, *hbox, *label;
138
139 pgs_info.min = min;
140
141 if(pgs_info.level == 0)
142 {
143 if(flags & PFLG_BUSY_MODE)
144 flags &= PFLG_BUSY_MODE; /* Clear any other flag. */
145
146 pgs_info.flags = flags;
147 pgs_info.show_byte = (flags & PFLG_BYTE_VISIBLE) ? TRUE : FALSE;
148 pgs_info.show_item = (flags & PFLG_ITEM_VISIBLE) ? TRUE : FALSE;
149 pgs_info.delayed = TRUE;
150 pgs_info.delay_time = 250000U;
151 pgs_info.byte_pos = 0;
152 pgs_info.last_secs = 0U;
153
154 if(pgs_info.cancel == NULL)
155 pgs_info.cancel = g_cancellable_new();
156 else
157 g_cancellable_reset(pgs_info.cancel);
158
159 if(flags & PFLG_COUNT_RECURSIVE)
160 pgs_info.tot_num = count_recursive_selected(min, &pgs_info.byte_tot);
161 else
162 pgs_info.tot_num = count_toplevel_selected(min, &pgs_info.byte_tot);
163
164 pgs_info.tot_pos = 0;
165
166 pgs_info.dlg = win_dialog_open(min->gui->window);
167 gtk_widget_set_size_request(pgs_info.dlg, 384, -1);
168 pgs_info.body = gtk_label_new(op_name);
169 g_signal_connect(G_OBJECT(pgs_info.dlg), "delete_event", G_CALLBACK(evt_delete), pgs_info.cancel);
170 g_signal_connect(G_OBJECT(pgs_info.dlg), "key_press_event", G_CALLBACK(evt_keypress), pgs_info.cancel);
171 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(pgs_info.dlg))), pgs_info.body, FALSE, FALSE, 0);
172
173 if(pgs_info.show_item)
174 {
175 pgs_info.item = gtk_label_new("");
176 gtk_label_set_xalign(GTK_LABEL(pgs_info.item), 0.f);
177 gtk_label_set_yalign(GTK_LABEL(pgs_info.item), 0.5f);
178 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(pgs_info.dlg))), pgs_info.item, FALSE, FALSE, 5);
179 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
180 pgs_info.item_pgs = gtk_progress_bar_new();
181 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(pgs_info.item_pgs), TRUE);
182 gtk_box_pack_start(GTK_BOX(hbox), pgs_info.item_pgs, TRUE, TRUE, 0);
183 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pgs_info.item_pgs), 0.f);
184 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(pgs_info.dlg))), hbox, FALSE, FALSE, 0);
185 }
186
187 pgs_info.tot_pgs = gtk_progress_bar_new();
188 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
189 if(flags & PFLG_BUSY_MODE)
190 {
191 gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(pgs_info.tot_pgs), 1.f / 10.f);
192 gtk_box_pack_start(GTK_BOX(hbox), pgs_info.tot_pgs, TRUE, TRUE, 0);
193 }
194 else
195 {
196 label = gtk_label_new("0");
197 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
198 gtk_box_pack_start(GTK_BOX(hbox), pgs_info.tot_pgs, TRUE, TRUE, 0);
199 g_snprintf(buf, sizeof buf, "%d", pgs_info.tot_num);
200 label = gtk_label_new(buf);
201 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 5);
202 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(pgs_info.tot_pgs), TRUE);
203 }
204 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pgs_info.tot_pgs), 0.f);
205 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(pgs_info.dlg))), hbox, FALSE, FALSE, 0);
206
207 if(pgs_info.show_byte)
208 {
209 sze_put_offset(size_buf, sizeof size_buf, pgs_info.byte_tot, SZE_AUTO, 3, ',');
210 g_snprintf(buf, sizeof buf, _("Total (%s)"), size_buf);
211 label = gtk_label_new(buf);
212 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(pgs_info.dlg))), label, FALSE, FALSE, 0);
213 pgs_info.byte_pgs = gtk_progress_bar_new();
214 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(pgs_info.byte_pgs), TRUE);
215 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(pgs_info.dlg))), pgs_info.byte_pgs, TRUE, TRUE, 0);
216 }
217 if(!(flags & PFLG_BUSY_MODE))
218 {
219 pgs_info.eta = gtk_label_new("");
220 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(pgs_info.dlg))), pgs_info.eta, FALSE, FALSE, 0);
221 }
222 else
223 pgs_info.eta = NULL;
224 cancel = gtk_button_new_with_label(_("Cancel"));
225 g_signal_connect(G_OBJECT(cancel), "clicked", G_CALLBACK(evt_cancel_clicked), pgs_info.cancel);
226 gtk_dialog_add_action_widget(GTK_DIALOG(pgs_info.dlg), cancel, GTK_RESPONSE_CANCEL);
227
228 g_get_current_time(&pgs_info.time_begin);
229 }
230 else
231 g_warning("pgs_progress_begin() doesn't nest");
232 pgs_info.level++;
233 }
234
pgs_progress_get_cancellable(void)235 GCancellable * pgs_progress_get_cancellable(void)
236 {
237 return pgs_info.cancel;
238 }
239
240 /* 1998-09-30 - End a progress-reporting session. Closes down the dialog box. */
pgs_progress_end(MainInfo * min)241 void pgs_progress_end(MainInfo *min)
242 {
243 if(pgs_info.level == 1)
244 {
245 gtk_widget_destroy(pgs_info.dlg);
246 pgs_info.dlg = NULL;
247 }
248 pgs_info.level--;
249 }
250
251 /* ----------------------------------------------------------------------------------------- */
252
253 /* 1998-09-30 - Begin on a new "item"; typically a file, being <size> bytes. */
pgs_progress_item_begin(MainInfo * min,const gchar * name,off_t size)254 void pgs_progress_item_begin(MainInfo *min, const gchar *name, off_t size)
255 {
256 if(pgs_info.flags & PFLG_BUSY_MODE)
257 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(pgs_info.tot_pgs));
258 else
259 {
260 gchar buf[32];
261 g_snprintf(buf, sizeof buf, "%u", pgs_info.tot_pos);
262 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pgs_info.tot_pgs), buf);
263 pgs_info.tot_pos++;
264 }
265 g_snprintf(pgs_info.name, sizeof pgs_info.name, "%s", name);
266 pgs_progress_item_resize(min, size);
267 }
268
269 /* 2012-01-09 - Set a new size for the current item. This makes sense when moving. */
pgs_progress_item_resize(MainInfo * min,off_t new_size)270 void pgs_progress_item_resize(MainInfo *min, off_t new_size)
271 {
272 pgs_info.item_size = new_size;
273
274 if(pgs_info.show_item)
275 {
276 gchar size_buf[32], buf[FILENAME_MAX + 32];
277
278 sze_put_offset(size_buf, sizeof size_buf, pgs_info.item_size, SZE_AUTO, 3, ',');
279 g_snprintf(buf, sizeof buf, "%s (%s)", pgs_info.name, size_buf);
280 gtk_label_set_text(GTK_LABEL(pgs_info.item), buf);
281 }
282 gui_events_flush();
283 }
284
285 /* 2014-03-02 - Format a bunch of seconds into a more friendly "time" format. */
format_eta(gchar * buf,gsize buf_max,guint seconds)286 static void format_eta(gchar *buf, gsize buf_max, guint seconds)
287 {
288 const guint base[] = { 60, 60, 24, 0 };
289 guint field[sizeof base / sizeof *base], i, conv = seconds;
290
291 for(i = 0; i < sizeof field / sizeof *field; ++i)
292 {
293 if(base[i])
294 {
295 field[i] = conv % base[i];
296 conv /= base[i];
297 }
298 else
299 field[i] = conv;
300 }
301
302 if(seconds < 60 * 60) /* Enough with MM:SS? */
303 g_snprintf(buf, buf_max, "%02u:%02u", field[1], field[0]);
304 else if(seconds < 24 * 60 * 60) /* Enough with HH:MM:SS? */
305 g_snprintf(buf, buf_max, "%02u:%02u:%02u", field[2], field[1], field[0]);
306 else if(field[3] == 1) /* FIXME: Perhaps there's better I18N magic to use here for day(s). */
307 g_snprintf(buf, buf_max, _("%u day, %02u:%02u:%02u"), field[3], field[2], field[1], field[0]);
308 else
309 g_snprintf(buf, buf_max, _("%u days, %02u:%02u:%02u"), field[3], field[2], field[1], field[0]);
310 }
311
312 /* 1998-09-30 - Indicate progress operating on the most recently registered item. Our
313 ** current position in the item is <pos> bytes.
314 */
pgs_progress_item_update(MainInfo * min,off_t pos)315 PgsRes pgs_progress_item_update(MainInfo *min, off_t pos)
316 {
317 GTimeVal time_now;
318
319 if(pgs_info.dlg != NULL)
320 {
321 g_get_current_time(&time_now);
322 if(pgs_info.delayed)
323 {
324 const guint micro = 1E6 * (time_now.tv_sec - pgs_info.time_begin.tv_sec) +
325 (time_now.tv_usec - pgs_info.time_begin.tv_usec);
326
327 if(micro >= pgs_info.delay_time)
328 pgs_info.delayed = FALSE;
329 }
330 if(!pgs_info.delayed)
331 {
332 if(pgs_info.eta != NULL)
333 {
334 const gfloat secs = msu_diff_timeval(&pgs_info.time_begin, &time_now);
335 if((guint) secs != pgs_info.last_secs)
336 {
337 gchar buf[64], spdbuf[16], etabuf[32];
338 gfloat spd;
339 guint eta;
340
341 pgs_info.last_secs = secs;
342 spd = (pgs_info.byte_pos + pos) / secs;
343 eta = (pgs_info.byte_tot - (pgs_info.byte_pos + pos)) / spd;
344 format_eta(etabuf, sizeof etabuf, eta);
345 sze_put_offset(spdbuf, sizeof spdbuf, spd, SZE_AUTO, 3, ',');
346 g_snprintf(buf, sizeof buf, _("Elapsed %02d:%02d Speed %s/s ETA %s"),
347 pgs_info.last_secs / 60, pgs_info.last_secs % 60, spdbuf,
348 etabuf);
349 gtk_label_set_text(GTK_LABEL(pgs_info.eta), buf);
350 }
351 }
352 gtk_widget_realize(pgs_info.dlg);
353 gdk_window_set_group(gtk_widget_get_window(pgs_info.dlg), gtk_widget_get_window(pgs_info.min->gui->window));
354 gtk_widget_show_all(pgs_info.dlg);
355 }
356 if(pgs_info.show_byte && pgs_info.byte_tot)
357 {
358 gchar tmp[32];
359 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pgs_info.byte_pgs), (gfloat) (pgs_info.byte_pos + pos) / pgs_info.byte_tot);
360 sze_put_offset(tmp, sizeof tmp, pgs_info.byte_pos + pos, SZE_AUTO, 3, ',');
361 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pgs_info.byte_pgs), tmp);
362 }
363 if(pgs_info.show_item && pgs_info.item_size)
364 {
365 gchar tmp[32];
366 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pgs_info.item_pgs), (gfloat) pos / pgs_info.item_size);
367 sze_put_offset(tmp, sizeof tmp, pos, SZE_AUTO, 3, ',');
368 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pgs_info.item_pgs), tmp);
369 }
370 gui_events_flush();
371 if(g_cancellable_is_cancelled(pgs_info.cancel))
372 return PGS_CANCEL;
373 }
374 return PGS_PROCEED;
375 }
376
377 /* 1998-09-30 - Done with an item. */
pgs_progress_item_end(MainInfo * min)378 void pgs_progress_item_end(MainInfo *min)
379 {
380 pgs_info.byte_pos += pgs_info.item_size;
381
382 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pgs_info.tot_pgs), (gfloat) pgs_info.tot_pos / pgs_info.tot_num);
383 gui_events_flush();
384 }
385