1 | /* rgfetchprogress.cc |
---|
2 | * |
---|
3 | * Copyright (c) 2000, 2001 Conectiva S/A |
---|
4 | * 2002, 2003 Michael Vogt <mvo@debian.org> |
---|
5 | * |
---|
6 | * Author: Alfredo K. Kojima <kojima@conectiva.com.br> |
---|
7 | * Michael Vogt <mvo@debian.org> |
---|
8 | * |
---|
9 | * This program is free software; you can redistribute it and/or |
---|
10 | * modify it under the terms of the GNU General Public License as |
---|
11 | * published by the Free Software Foundation; either version 2 of the |
---|
12 | * License, or (at your option) any later version. |
---|
13 | * |
---|
14 | * This program is distributed in the hope that it will be useful, |
---|
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
17 | * GNU General Public License for more details. |
---|
18 | * |
---|
19 | * You should have received a copy of the GNU General Public License |
---|
20 | * along with this program; if not, write to the Free Software |
---|
21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
---|
22 | * USA |
---|
23 | */ |
---|
24 | |
---|
25 | #include "config.h" |
---|
26 | |
---|
27 | #include <apt-pkg/acquire-item.h> |
---|
28 | #include <apt-pkg/acquire-worker.h> |
---|
29 | #include <apt-pkg/strutl.h> |
---|
30 | #include <apt-pkg/error.h> |
---|
31 | |
---|
32 | #include "rgfetchprogress.h" |
---|
33 | #include "rguserdialog.h" |
---|
34 | #include "rgmisc.h" |
---|
35 | |
---|
36 | #include <stdio.h> |
---|
37 | #include <pango/pango.h> |
---|
38 | #include <gtk/gtk.h> |
---|
39 | #include <cassert> |
---|
40 | |
---|
41 | #include "i18n.h" |
---|
42 | |
---|
43 | enum { |
---|
44 | DLDone = -1, |
---|
45 | DLQueued = -2, |
---|
46 | DLFailed = -3, |
---|
47 | DLHit = -4 |
---|
48 | }; |
---|
49 | |
---|
50 | enum { |
---|
51 | FETCH_PIXMAP_COLUMN, |
---|
52 | FETCH_SIZE_COLUMN, |
---|
53 | FETCH_DESCR_COLUMN, |
---|
54 | FETCH_URL_COLUMN, |
---|
55 | N_FETCH_COLUMNS |
---|
56 | }; |
---|
57 | |
---|
58 | static const int COLUMN_PERCENT_WIDTH=100; |
---|
59 | static const int COLUMN_PERCENT_HEIGHT=18; |
---|
60 | |
---|
61 | |
---|
62 | bool RGFetchProgress::close() |
---|
63 | { |
---|
64 | stopDownload(NULL, this); |
---|
65 | |
---|
66 | return TRUE; |
---|
67 | } |
---|
68 | |
---|
69 | RGFetchProgress::RGFetchProgress(RGWindow *win) |
---|
70 | : RGGladeWindow(win, "fetch"), _cursorDirty(false), _sock(NULL) |
---|
71 | { |
---|
72 | GtkCellRenderer *renderer; |
---|
73 | GtkTreeViewColumn *column; |
---|
74 | |
---|
75 | _mainProgressBar = glade_xml_get_widget(_gladeXML, "progressbar_download"); |
---|
76 | assert(_mainProgressBar); |
---|
77 | |
---|
78 | _table = glade_xml_get_widget(_gladeXML, "treeview_fetch"); |
---|
79 | _tableListStore = gtk_list_store_new(N_FETCH_COLUMNS, |
---|
80 | GDK_TYPE_PIXBUF, |
---|
81 | G_TYPE_STRING, G_TYPE_STRING, |
---|
82 | G_TYPE_STRING); |
---|
83 | gtk_tree_view_set_model(GTK_TREE_VIEW(_table), |
---|
84 | GTK_TREE_MODEL(_tableListStore)); |
---|
85 | |
---|
86 | /* percent column */ |
---|
87 | renderer = gtk_cell_renderer_pixbuf_new(); |
---|
88 | column = gtk_tree_view_column_new_with_attributes(_("Status"), renderer, |
---|
89 | "pixbuf", |
---|
90 | FETCH_PIXMAP_COLUMN, |
---|
91 | NULL); |
---|
92 | gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); |
---|
93 | gtk_tree_view_column_set_fixed_width(column, COLUMN_PERCENT_WIDTH); |
---|
94 | _statusColumn = column; |
---|
95 | _statusRenderer = renderer; |
---|
96 | gtk_tree_view_append_column(GTK_TREE_VIEW(_table), column); |
---|
97 | |
---|
98 | /* size */ |
---|
99 | renderer = gtk_cell_renderer_text_new(); |
---|
100 | column = gtk_tree_view_column_new_with_attributes(_("Size"), renderer, |
---|
101 | "text", FETCH_SIZE_COLUMN, |
---|
102 | NULL); |
---|
103 | gtk_tree_view_append_column(GTK_TREE_VIEW(_table), column); |
---|
104 | |
---|
105 | /* descr */ |
---|
106 | renderer = gtk_cell_renderer_text_new(); |
---|
107 | column = gtk_tree_view_column_new_with_attributes(_("Package"), renderer, |
---|
108 | "text",FETCH_DESCR_COLUMN, |
---|
109 | NULL); |
---|
110 | gtk_tree_view_append_column(GTK_TREE_VIEW(_table), column); |
---|
111 | |
---|
112 | /* url */ |
---|
113 | renderer = gtk_cell_renderer_text_new(); |
---|
114 | column = gtk_tree_view_column_new_with_attributes(_("URI"), renderer, |
---|
115 | "text", FETCH_URL_COLUMN, |
---|
116 | NULL); |
---|
117 | gtk_tree_view_append_column(GTK_TREE_VIEW(_table), column); |
---|
118 | |
---|
119 | |
---|
120 | glade_xml_signal_connect_data(_gladeXML, |
---|
121 | "on_button_cancel_clicked", |
---|
122 | G_CALLBACK(stopDownload), this); |
---|
123 | |
---|
124 | PangoContext *context = gdk_pango_context_get(); |
---|
125 | _layout = pango_layout_new(context); |
---|
126 | |
---|
127 | // check if we should run embedded somewhere |
---|
128 | // we need to make the window show before we obtain the _gc |
---|
129 | int id = _config->FindI("Volatile::PlugProgressInto", -1); |
---|
130 | //cout << "Plug ID: " << id << endl; |
---|
131 | if (id > 0) { |
---|
132 | gtk_widget_hide(glade_xml_get_widget(_gladeXML, "window_fetch")); |
---|
133 | GtkWidget *vbox = glade_xml_get_widget(_gladeXML, "vbox_fetch"); |
---|
134 | _sock = gtk_plug_new(id); |
---|
135 | gtk_widget_reparent(vbox, _sock); |
---|
136 | gtk_widget_show_all(_sock); |
---|
137 | _win = _sock; |
---|
138 | } |
---|
139 | show(); |
---|
140 | |
---|
141 | GtkStyle *style = gtk_widget_get_style(_win); |
---|
142 | _font = style->font_desc; |
---|
143 | _gc = style->white_gc; |
---|
144 | _barGC = style->bg_gc[0]; |
---|
145 | _textGC = style->black_gc; |
---|
146 | |
---|
147 | // emit a signal if the user changed the cursor |
---|
148 | g_signal_connect(G_OBJECT(_table), "cursor-changed", |
---|
149 | G_CALLBACK(cursorChanged), this); |
---|
150 | |
---|
151 | // emit a signal if the user changed the cursor |
---|
152 | GtkWidget *expander = glade_xml_get_widget(_gladeXML, "expander"); |
---|
153 | assert(expander); |
---|
154 | g_signal_connect (expander, "notify::expanded", |
---|
155 | G_CALLBACK (expanderActivate), this); |
---|
156 | |
---|
157 | } |
---|
158 | |
---|
159 | void RGFetchProgress::expanderActivate(GObject *object, |
---|
160 | GParamSpec *param_spec, |
---|
161 | gpointer data) |
---|
162 | { |
---|
163 | GtkExpander *expander = GTK_EXPANDER (object); |
---|
164 | RGFetchProgress *me = (RGFetchProgress*)data; |
---|
165 | |
---|
166 | GtkWidget *win = glade_xml_get_widget(me->_gladeXML, "window_fetch"); |
---|
167 | if (gtk_expander_get_expanded (expander)) |
---|
168 | gtk_window_set_resizable(GTK_WINDOW(win),TRUE); |
---|
169 | else |
---|
170 | gtk_window_set_resizable(GTK_WINDOW(win), FALSE); |
---|
171 | } |
---|
172 | |
---|
173 | |
---|
174 | void RGFetchProgress::cursorChanged(GtkTreeView *self, void *data) |
---|
175 | { |
---|
176 | //cout << "cursor-changed" << endl; |
---|
177 | RGFetchProgress *me = (RGFetchProgress *)data; |
---|
178 | me->_cursorDirty=true; |
---|
179 | } |
---|
180 | |
---|
181 | void RGFetchProgress::setDescription(string mainText, string secondText) |
---|
182 | { |
---|
183 | gtk_window_set_title(GTK_WINDOW(_win), mainText.c_str()); |
---|
184 | gchar *str = g_strdup_printf("<big><b>%s</b></big> \n\n%s", |
---|
185 | mainText.c_str(), secondText.c_str()); |
---|
186 | gtk_label_set_markup(GTK_LABEL(glade_xml_get_widget(_gladeXML, "label_description")), str); |
---|
187 | |
---|
188 | |
---|
189 | g_free(str); |
---|
190 | } |
---|
191 | |
---|
192 | bool RGFetchProgress::MediaChange(string Media, string Drive) |
---|
193 | { |
---|
194 | gchar *msg; |
---|
195 | |
---|
196 | msg = g_strdup_printf(_("Please insert the disk labeled:\n%s\nin drive %s"), |
---|
197 | Media.c_str(), Drive.c_str()); |
---|
198 | |
---|
199 | RGUserDialog userDialog(this); |
---|
200 | _cancelled = !userDialog.proceed(msg); |
---|
201 | |
---|
202 | RGFlushInterface(); |
---|
203 | g_free(msg); |
---|
204 | return true; |
---|
205 | |
---|
206 | |
---|
207 | |
---|
208 | #if 0 // this code can be used when apt fixed ubuntu #2281 (patch pending) |
---|
209 | bool res = !userDialog.proceed(msg); |
---|
210 | //cout << "Media change " << res << endl; |
---|
211 | |
---|
212 | RGFlushInterface(); |
---|
213 | g_free(msg); |
---|
214 | |
---|
215 | if(res) { |
---|
216 | return false; |
---|
217 | } else { |
---|
218 | Update = true; |
---|
219 | return true; |
---|
220 | } |
---|
221 | #endif |
---|
222 | } |
---|
223 | |
---|
224 | |
---|
225 | void RGFetchProgress::updateStatus(pkgAcquire::ItemDesc & Itm, int status) |
---|
226 | { |
---|
227 | //cout << "void RGFetchProgress::updateStatus()" << endl; |
---|
228 | |
---|
229 | if (Itm.Owner->ID == 0) { |
---|
230 | Item item; |
---|
231 | item.descr = Itm.ShortDesc; |
---|
232 | item.uri = Itm.Description; |
---|
233 | item.size = string(SizeToStr(Itm.Owner->FileSize)); |
---|
234 | item.status = status; |
---|
235 | _items.push_back(item); |
---|
236 | Itm.Owner->ID = _items.size(); |
---|
237 | refreshTable(Itm.Owner->ID - 1, true); |
---|
238 | } else if (_items[Itm.Owner->ID - 1].status != status) { |
---|
239 | _items[Itm.Owner->ID - 1].status = status; |
---|
240 | refreshTable(Itm.Owner->ID - 1, false); |
---|
241 | } |
---|
242 | } |
---|
243 | |
---|
244 | |
---|
245 | void RGFetchProgress::IMSHit(pkgAcquire::ItemDesc & Itm) |
---|
246 | { |
---|
247 | //cout << "void RGFetchProgress::IMSHit(pkgAcquire::ItemDesc &Itm)" << endl; |
---|
248 | updateStatus(Itm, DLHit); |
---|
249 | |
---|
250 | RGFlushInterface(); |
---|
251 | } |
---|
252 | |
---|
253 | |
---|
254 | void RGFetchProgress::Fetch(pkgAcquire::ItemDesc & Itm) |
---|
255 | { |
---|
256 | updateStatus(Itm, DLQueued); |
---|
257 | |
---|
258 | RGFlushInterface(); |
---|
259 | } |
---|
260 | |
---|
261 | |
---|
262 | void RGFetchProgress::Done(pkgAcquire::ItemDesc & Itm) |
---|
263 | { |
---|
264 | updateStatus(Itm, DLDone); |
---|
265 | |
---|
266 | RGFlushInterface(); |
---|
267 | } |
---|
268 | |
---|
269 | |
---|
270 | void RGFetchProgress::Fail(pkgAcquire::ItemDesc & Itm) |
---|
271 | { |
---|
272 | if (Itm.Owner->Status == pkgAcquire::Item::StatIdle) |
---|
273 | return; |
---|
274 | |
---|
275 | updateStatus(Itm, DLFailed); |
---|
276 | |
---|
277 | RGFlushInterface(); |
---|
278 | } |
---|
279 | |
---|
280 | |
---|
281 | bool RGFetchProgress::Pulse(pkgAcquire * Owner) |
---|
282 | { |
---|
283 | //cout << "RGFetchProgress::Pulse(pkgAcquire *Owner)" << endl; |
---|
284 | |
---|
285 | pkgAcquireStatus::Pulse(Owner); |
---|
286 | |
---|
287 | for (pkgAcquire::Worker * I = Owner->WorkersBegin(); I != 0; |
---|
288 | I = Owner->WorkerStep(I)) { |
---|
289 | |
---|
290 | if (I->CurrentItem == 0) |
---|
291 | continue; |
---|
292 | |
---|
293 | if (I->TotalSize > 0) |
---|
294 | updateStatus(*I->CurrentItem, |
---|
295 | long (double (I->CurrentSize * 100.0) / |
---|
296 | double (I->TotalSize))); |
---|
297 | else |
---|
298 | updateStatus(*I->CurrentItem, 100); |
---|
299 | |
---|
300 | } |
---|
301 | |
---|
302 | float percent = |
---|
303 | long (double ((CurrentBytes + CurrentItems) * 100.0) / |
---|
304 | double (TotalBytes + TotalItems)); |
---|
305 | // work-around a stupid problem with libapt |
---|
306 | if(CurrentItems == TotalItems) |
---|
307 | percent=100.0; |
---|
308 | |
---|
309 | gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(_mainProgressBar), |
---|
310 | percent / 100.0); |
---|
311 | |
---|
312 | unsigned long ETA = |
---|
313 | (unsigned long)((TotalBytes - CurrentBytes) / CurrentCPS); |
---|
314 | long i = CurrentItems < TotalItems ? CurrentItems + 1 : CurrentItems; |
---|
315 | gchar *s; |
---|
316 | if (CurrentCPS != 0 && ETA != 0) { |
---|
317 | s = g_strdup_printf(_("Download rate: %s/s - %s remaining"), |
---|
318 | SizeToStr(CurrentCPS).c_str(), |
---|
319 | TimeToStr(ETA).c_str()); |
---|
320 | gtk_label_set_text(GTK_LABEL(glade_xml_get_widget(_gladeXML, "label_eta")),s); |
---|
321 | g_free(s); |
---|
322 | } else { |
---|
323 | gtk_label_set_text(GTK_LABEL(glade_xml_get_widget(_gladeXML, "label_eta")),_("Download rate: unknown")); |
---|
324 | } |
---|
325 | s = g_strdup_printf(_("Downloading file %li of %li"), i, TotalItems); |
---|
326 | gtk_progress_bar_set_text(GTK_PROGRESS_BAR(_mainProgressBar), s); |
---|
327 | g_free(s); |
---|
328 | |
---|
329 | RGFlushInterface(); |
---|
330 | |
---|
331 | return !_cancelled; |
---|
332 | } |
---|
333 | |
---|
334 | |
---|
335 | void RGFetchProgress::Start() |
---|
336 | { |
---|
337 | //cout << "RGFetchProgress::Start()" << endl; |
---|
338 | pkgAcquireStatus::Start(); |
---|
339 | _cancelled = false; |
---|
340 | show(); |
---|
341 | |
---|
342 | RGFlushInterface(); |
---|
343 | } |
---|
344 | |
---|
345 | |
---|
346 | void RGFetchProgress::Stop() |
---|
347 | { |
---|
348 | //cout << "RGFetchProgress::Stop()" << endl; |
---|
349 | RGFlushInterface(); |
---|
350 | if(_sock != NULL) { |
---|
351 | gtk_widget_destroy(_sock); |
---|
352 | } else { |
---|
353 | hide(); |
---|
354 | } |
---|
355 | pkgAcquireStatus::Stop(); |
---|
356 | |
---|
357 | //FIXME: this needs to be handled in a better way (gtk-2 maybe?) |
---|
358 | sleep(1); // this sucks, but if ommited, the window will not always |
---|
359 | // closed (e.g. when a package is only deleted) |
---|
360 | RGFlushInterface(); |
---|
361 | } |
---|
362 | |
---|
363 | |
---|
364 | |
---|
365 | void RGFetchProgress::stopDownload(GtkWidget *self, void *data) |
---|
366 | { |
---|
367 | RGFetchProgress *me = (RGFetchProgress *) data; |
---|
368 | |
---|
369 | me->_cancelled = true; |
---|
370 | } |
---|
371 | |
---|
372 | |
---|
373 | GdkPixmap *RGFetchProgress::statusDraw(int width, int height, int status) |
---|
374 | { |
---|
375 | int x, y; |
---|
376 | char *str = ""; |
---|
377 | GdkPixmap *pix; |
---|
378 | int px, pw; |
---|
379 | |
---|
380 | pix = gdk_pixmap_new(_win->window, width, height, -1); |
---|
381 | |
---|
382 | px = 0; |
---|
383 | pw = status * width / 100; |
---|
384 | |
---|
385 | if (status < 0) { |
---|
386 | if (status == DLDone || status == DLHit) |
---|
387 | gdk_draw_rectangle(pix, _barGC, TRUE, px, 0, pw, height); |
---|
388 | else |
---|
389 | gdk_draw_rectangle(pix, _gc, TRUE, px, 0, pw, height); |
---|
390 | |
---|
391 | switch (status) { |
---|
392 | case DLQueued: |
---|
393 | str = _("Queued"); |
---|
394 | break; |
---|
395 | case DLDone: |
---|
396 | str = _("Done"); |
---|
397 | break; |
---|
398 | case DLHit: |
---|
399 | str = _("Hit"); |
---|
400 | break; |
---|
401 | case DLFailed: |
---|
402 | str = _("Failed"); |
---|
403 | break; |
---|
404 | } |
---|
405 | } else { |
---|
406 | static char buf[16]; |
---|
407 | |
---|
408 | gdk_draw_rectangle(pix, _barGC, TRUE, px, 0, pw, height); |
---|
409 | gdk_draw_rectangle(pix, _gc, TRUE, px + pw, 0, width - pw - 2, height); |
---|
410 | |
---|
411 | snprintf(buf, sizeof(buf), "%d%%", status); |
---|
412 | str = buf; |
---|
413 | } |
---|
414 | |
---|
415 | pango_layout_set_font_description(_layout, _font); |
---|
416 | pango_layout_set_text(_layout, str, -1); |
---|
417 | pango_layout_get_pixel_size(_layout, &x, &y); |
---|
418 | x = (width - x) / 2; |
---|
419 | gdk_draw_layout(pix, _textGC, x, 0, _layout); |
---|
420 | |
---|
421 | return pix; |
---|
422 | } |
---|
423 | |
---|
424 | |
---|
425 | void RGFetchProgress::refreshTable(int row, bool append) |
---|
426 | { |
---|
427 | //cout << "RGFetchProgress::refreshTable() " << row << endl; |
---|
428 | GtkTreeIter iter; |
---|
429 | static GdkPixmap *pix = NULL; |
---|
430 | static GdkPixbuf *buf = NULL; |
---|
431 | int w, h; |
---|
432 | |
---|
433 | // unref pix first (they start with a usage count of 1 |
---|
434 | // why not unref'ing it after adding in the table? -- niemeyer |
---|
435 | if (pix != NULL) |
---|
436 | gdk_pixmap_unref(pix); |
---|
437 | if (buf != NULL) |
---|
438 | gdk_pixbuf_unref(buf); |
---|
439 | |
---|
440 | w = COLUMN_PERCENT_WIDTH; //gtk_tree_view_column_get_width(_statusColumn); |
---|
441 | h = COLUMN_PERCENT_HEIGHT; // FIXME: height -> get it from somewhere |
---|
442 | |
---|
443 | pix = statusDraw(w, h, _items[row].status); |
---|
444 | buf = gdk_pixbuf_get_from_drawable(NULL, pix, NULL, 0, 0, 0, 0, w, h); |
---|
445 | GtkTreePath *path; |
---|
446 | if (append == true) { |
---|
447 | gtk_list_store_insert(_tableListStore, &iter, row); |
---|
448 | path = gtk_tree_path_new(); |
---|
449 | gtk_tree_path_append_index(path, row); |
---|
450 | //gtk_tree_view_set_cursor(GTK_TREE_VIEW(_table), path, NULL, false); |
---|
451 | if(!_cursorDirty) |
---|
452 | gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(_table), |
---|
453 | path, NULL, TRUE, 0.0, 0.0); |
---|
454 | gtk_tree_path_free(path); |
---|
455 | // can't we use the iterator here? |
---|
456 | } else { |
---|
457 | gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(_tableListStore), |
---|
458 | &iter, NULL, row); |
---|
459 | } |
---|
460 | gtk_list_store_set(_tableListStore, &iter, |
---|
461 | FETCH_PIXMAP_COLUMN, buf, |
---|
462 | FETCH_SIZE_COLUMN, _items[row].size.c_str(), |
---|
463 | FETCH_DESCR_COLUMN, _items[row].descr.c_str(), |
---|
464 | FETCH_URL_COLUMN, _items[row].uri.c_str(), -1); |
---|
465 | path = gtk_tree_model_get_path(GTK_TREE_MODEL(_tableListStore), &iter); |
---|
466 | if(!_cursorDirty) |
---|
467 | gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(_table), |
---|
468 | path, NULL, TRUE, 0.0, 0.0); |
---|
469 | gtk_tree_path_free(path); |
---|
470 | } |
---|
471 | |
---|
472 | // vim:sts=4:sw=4 |
---|