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