Merge lp:~charlesk/indicator-transfer/better-content-hub-use into lp:indicator-transfer/14.10

Proposed by Charles Kerr
Status: Merged
Approved by: Ted Gould
Approved revision: 20
Merged at revision: 16
Proposed branch: lp:~charlesk/indicator-transfer/better-content-hub-use
Merge into: lp:indicator-transfer/14.10
Diff against target: 1352 lines (+669/-417)
8 files modified
CMakeLists.txt (+8/-5)
debian/control (+3/-0)
include/transfer/transfer.h (+2/-1)
include/transfer/world-dbus.h (+2/-13)
src/view-console.cpp (+1/-1)
src/view-gmenu.cpp (+20/-7)
src/world-dbus.cpp (+616/-373)
tests/manual (+17/-17)
To merge this branch: bzr merge lp:~charlesk/indicator-transfer/better-content-hub-use
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Ted Gould (community) Approve
Review via email: mp+232144@code.launchpad.net

Commit message

Better use of information provided by content-hub.

Description of the change

=== Description of the Change

Better use of information provided by content-hub + only show downloads that work with content-hub.

1. Don't show transfers that don't go through content-hub. In particular, wait until a DownloadIdChanged signal is received from content-hub so that the content-hub and ubuntu-download-manager objects can be collated. (bug #1350771) (bug #1350307)

2. Use the transfer's peer's click manifest's icon for the menuitem's icon. (bug #1361347)

3. When we have a destination uri available, use its basename() as the menuitem's title. This currently is available only after the download is completed; getting it earlier will come in another patch when the information is available from content-hub or download-manager.

4. If the clears a transfer off of the indicator, don't bring it back even if more signals are received for the corresponding object from content-hub or download-manager. (bug #1348170)

5. implement Transfer::open() (using content-hub's Charge() method) and Transfer::open_app() using ubunbu-app-launch for the transfer's peer. (bug #1361363)

6. If a download object on the bus says that the amount downloaded exceeds the total size, stop estimating the ETA since we don't know how much is left to be downloaded. (bug #1348162)

=== Merge Proposal Checklist

> Are there any related MPs required for this MP to build/function as expected? Please list.

Not directly. However, be aware when you test that there's an outstanding unity8 regression (tracked in bug #1350308) that prevents rendering from being acceptable, both with and without this MP.

> Is your branch in sync with latest trunk? (e.g. bzr pull lp:trunk -> no changes)

Yes

> Did the code build without warnings?

Yes

> Did the tests run successfully?

Yes

> Did you perform an exploratory manual test run of your code change and any related functionality?

Yes, utopic image 210 on a mako device

> Has your component test plan been executed successfully on emulator or a physical device?

Yes

> Please list which manual tests are germane for the reviewer in this MP.

indicator-transfer/simple-download-check

When testing, please note the comment above about rendering.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ted Gould (ted) wrote :

Few comments.

review: Needs Fixing
19. By Charles Kerr

simplify the download speed estimation.

20. By Charles Kerr

fix the error-checking when trying to get an icon from click's manifest

Revision history for this message
Charles Kerr (charlesk) :
Revision history for this message
Ted Gould (ted) wrote :

Great!

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-06-17 01:36:16 +0000
3+++ CMakeLists.txt 2014-08-26 20:52:17 +0000
4@@ -31,11 +31,14 @@
5 find_package (PkgConfig REQUIRED)
6 include (FindPkgConfig)
7
8-pkg_check_modules (SERVICE_DEPS REQUIRED
9- glib-2.0>=2.36
10- gio-unix-2.0>=2.36
11- properties-cpp>=0.0.1)
12-include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
13+pkg_check_modules(SERVICE_DEPS REQUIRED
14+ glib-2.0>=2.36
15+ gio-unix-2.0>=2.36
16+ properties-cpp>=0.0.1
17+ click-0.4>=0.4.30
18+ json-glib-1.0
19+ ubuntu-app-launch-2)
20+include_directories(SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
21
22 ##
23 ## custom targets
24
25=== modified file 'debian/control'
26--- debian/control 2014-06-25 15:34:10 +0000
27+++ debian/control 2014-08-26 20:52:17 +0000
28@@ -8,6 +8,9 @@
29 g++-4.9,
30 libglib2.0-dev (>= 2.35.4),
31 libproperties-cpp-dev,
32+ libclick-0.4-dev (>= 0.4.30),
33+ libjson-glib-dev,
34+ libubuntu-app-launch2-dev,
35 # for coverage reports
36 lcov,
37 # for tests
38
39=== modified file 'include/transfer/transfer.h'
40--- include/transfer/transfer.h 2014-06-25 22:48:12 +0000
41+++ include/transfer/transfer.h 2014-08-26 20:52:17 +0000
42@@ -56,7 +56,8 @@
43 // [0...1]
44 float progress = 0.0;
45
46- uint64_t speed_bps = 0;
47+ // bytes per second
48+ uint64_t speed_Bps = 0;
49
50 uint64_t total_size = 0;
51
52
53=== modified file 'include/transfer/world-dbus.h'
54--- include/transfer/world-dbus.h 2014-06-17 01:36:16 +0000
55+++ include/transfer/world-dbus.h 2014-08-26 20:52:17 +0000
56@@ -47,19 +47,8 @@
57 void open_app(const Transfer::Id& id);
58
59 private:
60- GDBusConnection* m_bus = nullptr;
61- GCancellable* m_cancellable = nullptr;
62- std::set<guint> m_signal_subscriptions;
63- std::shared_ptr<MutableModel> m_model;
64-
65- void set_bus(GDBusConnection*);
66- void add_transfer(const char* object_path);
67-
68- static void on_bus_ready(GObject*, GAsyncResult*, gpointer);
69- static void on_progress(GObject*, GAsyncResult*, gpointer);
70- static void on_total_size(GObject*, GAsyncResult*, gpointer);
71- static void on_download_signal(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant*, gpointer);
72- static void on_download_created(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant*, gpointer);
73+ class Impl;
74+ std::unique_ptr<Impl> impl;
75 };
76
77 } // namespace transfer
78
79=== modified file 'src/view-console.cpp'
80--- src/view-console.cpp 2014-06-17 01:36:16 +0000
81+++ src/view-console.cpp 2014-08-26 20:52:17 +0000
82@@ -56,7 +56,7 @@
83 transfer->app_icon.c_str(),
84 (size_t)transfer->time_started,
85 transfer->seconds_left,
86- transfer->speed_bps/1024.0,
87+ transfer->speed_Bps/1024.0,
88 transfer->progress,
89 transfer->error_string.c_str(),
90 transfer->local_path.c_str());
91
92=== modified file 'src/view-gmenu.cpp'
93--- src/view-gmenu.cpp 2014-07-09 21:18:50 +0000
94+++ src/view-gmenu.cpp 2014-08-26 20:52:17 +0000
95@@ -152,8 +152,9 @@
96 if (transfer)
97 {
98 g_variant_builder_add(&b, "{sv}", "percent",
99- g_variant_new_double(transfer->progress));
100- if (transfer->seconds_left >= 0)
101+ g_variant_new_double(CLAMP(transfer->progress, 0.0, 1.0)));
102+
103+ if ((transfer->seconds_left >= 0) && (int(transfer->progress*100.0) < 100))
104 {
105 g_variant_builder_add(&b, "{sv}", "seconds-left",
106 g_variant_new_int32(transfer->seconds_left));
107@@ -612,11 +613,23 @@
108 g_menu_item_set_attribute (menu_item, ATTRIBUTE_X_TYPE,
109 "s", "com.canonical.indicator.transfer");
110
111- //FIXME: this is a placeholder
112- auto icon = g_themed_icon_new("image-missing");
113- auto v = g_icon_serialize(icon);
114- g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ICON, v);
115- g_object_unref(icon);
116+ GVariant * serialized_icon = nullptr;
117+ if (!t->app_icon.empty() && g_file_test(t->app_icon.c_str(), G_FILE_TEST_EXISTS))
118+ {
119+ auto file = g_file_new_for_path(t->app_icon.c_str());
120+ auto icon = g_file_icon_new(file);
121+ serialized_icon = g_icon_serialize(icon);
122+ g_clear_object(&icon);
123+ g_clear_object(&file);
124+ }
125+ if (serialized_icon == nullptr)
126+ {
127+ auto icon = g_themed_icon_new("image-missing");
128+ serialized_icon = g_icon_serialize(icon);
129+ g_clear_object(&icon);
130+ }
131+ g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ICON, serialized_icon);
132+ g_variant_unref(serialized_icon);
133
134 g_menu_item_set_attribute (menu_item, ATTRIBUTE_X_UID, "s", id);
135 g_menu_item_set_action_and_target_value (menu_item,
136
137=== modified file 'src/world-dbus.cpp'
138--- src/world-dbus.cpp 2014-07-09 22:33:01 +0000
139+++ src/world-dbus.cpp 2014-08-26 20:52:17 +0000
140@@ -19,6 +19,12 @@
141
142 #include <transfer/world-dbus.h>
143
144+#include <click.h>
145+#include <ubuntu-app-launch.h>
146+
147+#include <json-glib/json-glib.h>
148+
149+#include <algorithm>
150 #include <iostream>
151
152 namespace unity {
153@@ -27,25 +33,41 @@
154
155 namespace {
156
157-#define BUS_NAME "com.canonical.applications.Downloader"
158-#define DOWNLOAD_MANAGER_IFACE_NAME "com.canonical.applications.DownloadManager"
159-#define DOWNLOAD_IFACE_NAME "com.canonical.applications.Download"
160-
161+static constexpr char const * DM_BUS_NAME {"com.canonical.applications.Downloader"};
162+static constexpr char const * DM_MANAGER_IFACE_NAME {"com.canonical.applications.DownloadManager"};
163+static constexpr char const * DM_DOWNLOAD_IFACE_NAME {"com.canonical.applications.Download"};
164+
165+static constexpr char const * CH_BUS_NAME {"com.ubuntu.content.dbus.Service"};
166+static constexpr char const * CH_TRANSFER_IFACE_NAME {"com.ubuntu.content.dbus.Transfer"};
167+
168+
169+/**
170+ * A Transfer whose state comes from content-hub and ubuntu-download-manager.
171+ *
172+ * Each DBusTransfer tracks a com.canonical.applications.Download (ccad) object
173+ * from ubuntu-download-manager. The ccad is used for pause/resume/cancel,
174+ * state change / download progress signals, etc.
175+ *
176+ * Each DBusTransfer also tracks a com.ubuntu.content.dbus.Transfer (cucdt)
177+ * object from content-hub. The cucdt is used for learning the download's peer
178+ * and for calling Charge() to launch the peer's app.
179+ */
180 class DBusTransfer: public Transfer
181 {
182 public:
183
184- DBusTransfer(GDBusConnection* connection, const char* object_path):
185+ DBusTransfer(GDBusConnection* connection,
186+ const std::string& ccad_path,
187+ const std::string& cucdt_path):
188 m_bus(G_DBUS_CONNECTION(g_object_ref(connection))),
189 m_cancellable(g_cancellable_new()),
190- m_object_path(object_path)
191+ m_ccad_path(ccad_path),
192+ m_cucdt_path(cucdt_path)
193 {
194- g_debug("creating a new DBusTransfer for '%s'", object_path);
195-
196 id = next_unique_id();
197 time_started = time(nullptr);
198-
199- get_properties_from_bus();
200+ get_ccad_properties();
201+ get_cucdt_properties();
202 }
203
204 ~DBusTransfer()
205@@ -59,39 +81,82 @@
206
207 void start()
208 {
209- g_return_if_fail (can_start());
210+ g_return_if_fail(can_start());
211
212- call_method_no_args_no_response ("start");
213+ call_ccad_method_no_args_no_response("start");
214 }
215
216 void pause()
217 {
218- g_return_if_fail (can_pause());
219+ g_return_if_fail(can_pause());
220
221- call_method_no_args_no_response("pause");
222+ call_ccad_method_no_args_no_response("pause");
223 }
224
225 void resume()
226 {
227 g_return_if_fail(can_resume());
228
229- call_method_no_args_no_response("resume");
230+ call_ccad_method_no_args_no_response("resume");
231 }
232
233 void cancel()
234 {
235- call_method_no_args_no_response("cancel");
236- }
237-
238- const std::string& object_path() const
239- {
240- return m_object_path;
241- }
242-
243- void handle_signal(const gchar* signal_name, GVariant* parameters)
244- {
245- g_debug ("%s transfer %s got signal '%s'", G_STRLOC, id.c_str(), signal_name);
246-
247+ call_ccad_method_no_args_no_response("cancel");
248+ }
249+
250+ void open()
251+ {
252+ /* Once a transfer is complete, an app can be launched to process
253+ it by calling Charge() with an empty variant list argument */
254+ g_return_if_fail(!m_cucdt_path.empty());
255+ g_dbus_connection_call(m_bus,
256+ CH_BUS_NAME,
257+ m_cucdt_path.c_str(),
258+ CH_TRANSFER_IFACE_NAME,
259+ "Charge",
260+ g_variant_new("(av)", nullptr),
261+ nullptr,
262+ G_DBUS_CALL_FLAGS_NONE,
263+ -1, // default timeout
264+ m_cancellable,
265+ on_cucdt_charge, // callback
266+ nullptr); // callback user_data
267+ }
268+
269+ void open_app()
270+ {
271+ g_return_if_fail(!m_peer_name.empty());
272+
273+ gchar* app_id = ubuntu_app_launch_triplet_to_app_id(m_peer_name.c_str(), nullptr, nullptr);
274+ g_debug("calling ubuntu_app_launch_start_application() for %s", app_id);
275+ ubuntu_app_launch_start_application(app_id, nullptr);
276+ g_free(app_id);
277+ }
278+
279+ const std::string& cucdt_path() const
280+ {
281+ return m_cucdt_path;
282+ }
283+
284+ const std::string& ccad_path() const
285+ {
286+ return m_ccad_path;
287+ }
288+
289+ void handle_cucdt_signal(const gchar* signal_name, GVariant* parameters)
290+ {
291+ if (!g_strcmp0(signal_name, "StoreChanged"))
292+ {
293+ const gchar* uri = nullptr;
294+ g_variant_get_child(parameters, 0, "&s", &uri);
295+ if (uri != nullptr)
296+ set_store(uri);
297+ }
298+ }
299+
300+ void handle_ccad_signal(const gchar* signal_name, GVariant* parameters)
301+ {
302 if (!g_strcmp0(signal_name, "started"))
303 {
304 if (get_signal_success_arg(parameters))
305@@ -151,7 +216,7 @@
306 int32_t i;
307 char* str = nullptr;
308 g_variant_get_child(parameters, 0, "(is)", &i, &str);
309- g_message("%s setting error to '%s'", G_STRLOC, str);
310+ g_debug("%s setting error to '%s'", G_STRLOC, str);
311 set_error_string(str);
312 set_state(ERROR);
313 g_free(str);
314@@ -166,9 +231,19 @@
315
316 private:
317
318- // the 'started', 'paused', 'resumed', and 'canceled' signals
319- // from com.canonical.applications.Download all have a single
320- // parameter, a boolean success flag.
321+ static void on_cucdt_charge(GObject * source,
322+ GAsyncResult * res,
323+ gpointer /*unused*/)
324+ {
325+ auto v = connection_call_finish(source, res, "Error calling Charge()");
326+ g_clear_pointer(&v, g_variant_unref);
327+ }
328+
329+ void emit_changed() { changed()(); }
330+
331+ /* The 'started', 'paused', 'resumed', and 'canceled' signals
332+ from com.canonical.applications.Download all have a single
333+ parameter, a boolean success flag. */
334 bool get_signal_success_arg(GVariant* parameters)
335 {
336 gboolean success = false;
337@@ -178,70 +253,32 @@
338 return success;
339 }
340
341- void call_method_no_args_no_response(const char* method_name)
342- {
343- g_debug ("%s transfer %s calling '%s'", G_STRLOC, id.c_str(), method_name);
344-
345- g_dbus_connection_call(m_bus, // connection
346- BUS_NAME, // bus_name
347- m_object_path.c_str(), // object path
348- DOWNLOAD_IFACE_NAME, // interface name
349- method_name, // method name
350- nullptr, // parameters
351- nullptr, // reply type
352- G_DBUS_CALL_FLAGS_NONE, // flags
353- -1, // default timeout
354- m_cancellable, // cancellable
355- nullptr, // callback
356- nullptr /*user data*/);
357- }
358-
359-
360- core::Signal<> m_changed;
361- void emit_changed() { changed()(); }
362-
363- uint64_t m_received = 0;
364- uint64_t m_total_size = 0;
365- std::vector<std::pair<GTimeVal,uint64_t>> m_history;
366-
367- static double get_time_from_timeval(const GTimeVal& t)
368- {
369- return t.tv_sec + (t.tv_usec / (double)G_USEC_PER_SEC);
370- }
371-
372- // gets an averaged speed based on information saved from
373- // recent calls to set_progress()
374- uint64_t get_average_speed_bps()
375- {
376- unsigned int n_samples = 0;
377- uint64_t sum = 0;
378-
379- // prune out samples by size
380+ uint64_t get_averaged_speed_Bps()
381+ {
382+ // limit the average to the last X samples
383 static constexpr int max_slots = 50;
384 if (m_history.size() > max_slots)
385 m_history.erase(m_history.begin(), m_history.end()-max_slots);
386
387- static constexpr time_t max_age_seconds = 30;
388- GTimeVal now_tv;
389- g_get_current_time(&now_tv);
390- const double now = get_time_from_timeval(now_tv);
391- for(unsigned i=1; i<m_history.size(); i++)
392- {
393- const double begin_time = get_time_from_timeval(m_history[i-1].first);
394- if (now - begin_time > max_age_seconds)
395- continue;
396-
397- const double end_time = get_time_from_timeval(m_history[i].first);
398- const double time_diff = end_time - begin_time;
399- const uint64_t received_diff = m_history[i].second -
400- m_history[i-1].second;
401- const uint64_t bps = uint64_t(received_diff / time_diff);
402-
403- sum += bps;
404- ++n_samples;
405- }
406-
407- return n_samples ? sum / n_samples : 0;
408+ // limit the average to the last Y seconds
409+ static constexpr unsigned int max_age_seconds = 30;
410+ const auto oldest_allowed_usec = g_get_real_time() - (max_age_seconds * G_USEC_PER_SEC);
411+ const auto is_young = [oldest_allowed_usec](const DownloadProgress& p){return p.time_usec >= oldest_allowed_usec;};
412+ m_history.erase(std::begin(m_history), std::find_if(std::begin(m_history), std::end(m_history), is_young));
413+
414+ uint64_t Bps;
415+
416+ if (m_history.size() < 2)
417+ {
418+ Bps = 0;
419+ }
420+ else
421+ {
422+ const auto diff = m_history.back() - m_history.front();
423+ Bps = (diff.bytes * G_USEC_PER_SEC) / diff.time_usec;
424+ }
425+
426+ return Bps;
427 }
428
429 void update_progress()
430@@ -249,27 +286,25 @@
431 auto tmp_total_size = total_size;
432 auto tmp_progress = progress;
433 auto tmp_seconds_left = seconds_left;
434- auto tmp_speed_bps = speed_bps;
435+ auto tmp_speed_Bps = speed_Bps;
436
437 if (m_total_size && m_received)
438 {
439 // update our speed tables
440- GTimeVal now;
441- g_get_current_time(&now);
442- m_history.push_back(std::pair<GTimeVal,uint64_t>(now, m_received));
443+ m_history.push_back(DownloadProgress{g_get_real_time(),m_received});
444
445- const auto bps = get_average_speed_bps();
446- const int seconds = bps ? (int)((m_total_size - m_received) / bps) : -1;
447+ const auto Bps = get_averaged_speed_Bps();
448+ const int seconds = Bps ? (int)((m_total_size - m_received) / Bps) : -1;
449
450 tmp_total_size = m_total_size;
451- tmp_speed_bps = bps;
452+ tmp_speed_Bps = Bps;
453 tmp_progress = m_received / (float)m_total_size;
454 tmp_seconds_left = seconds;
455 }
456 else
457 {
458 tmp_total_size = 0;
459- tmp_speed_bps = 0;
460+ tmp_speed_Bps = 0;
461 tmp_progress = 0.0;
462 tmp_seconds_left = -1;
463 }
464@@ -284,13 +319,14 @@
465
466 if (seconds_left != tmp_seconds_left)
467 {
468+ g_debug("changing '%s' seconds_left to '%d'", m_ccad_path.c_str(), (int)tmp_seconds_left);
469 seconds_left = tmp_seconds_left;
470 changed = true;
471 }
472
473- if (speed_bps != tmp_speed_bps)
474+ if (speed_Bps != tmp_speed_Bps)
475 {
476- speed_bps = tmp_speed_bps;
477+ speed_Bps = tmp_speed_Bps;
478 changed = true;
479 }
480
481@@ -312,7 +348,7 @@
482
483 if (!can_pause())
484 {
485- speed_bps = 0;
486+ speed_Bps = 0;
487 m_history.clear();
488 }
489
490@@ -325,6 +361,7 @@
491 const std::string tmp = str ? str : "";
492 if (error_string != tmp)
493 {
494+ g_debug("changing '%s' error to '%s'", m_ccad_path.c_str(), tmp.c_str());
495 error_string = tmp;
496 emit_changed();
497 }
498@@ -334,60 +371,155 @@
499 {
500 const std::string tmp = str ? str : "";
501 if (local_path != tmp)
502- {
503- local_path = tmp;
504- emit_changed();
505- }
506- }
507-
508- /***
509- **** DBUS
510- ***/
511-
512- GDBusConnection* m_bus = nullptr;
513- GCancellable* m_cancellable = nullptr;
514- const std::string m_object_path;
515-
516- void get_properties_from_bus()
517- {
518- const auto bus_name = BUS_NAME;
519- const auto object_path = m_object_path.c_str();
520- const auto interface_name = DOWNLOAD_IFACE_NAME;
521+ {
522+ g_debug("changing '%s' path to '%s'", m_ccad_path.c_str(), tmp.c_str());
523+ local_path = tmp;
524+ emit_changed();
525+ }
526+
527+ // If we don't already have a title,
528+ // use the file's basename as the title
529+ if (title.empty())
530+ {
531+ auto bname = g_path_get_basename(str);
532+ set_title(bname);
533+ g_free(bname);
534+ }
535+ }
536+
537+ void set_title(const char* title_in)
538+ {
539+ const std::string tmp = title_in ? title_in : "";
540+ if (title != tmp)
541+ {
542+ g_debug("changing '%s' title to '%s'", m_ccad_path.c_str(), tmp.c_str());
543+ title = tmp;
544+ emit_changed();
545+ }
546+ }
547+
548+ void set_store(const char* store)
549+ {
550+ /**
551+ * This is a workaround until content-hub exposes a peer getter.
552+ * As an interim step, this code sniffs the peer by looking at the store.
553+ * the peer's listed in the directory component before HubIncoming, e.g.:
554+ * "/home/phablet/.cache/com.ubuntu.gallery/HubIncoming/4"
555+ */
556+ char** strv = g_strsplit(store, "/", -1);
557+ int i=0;
558+ for ( ; strv && strv[i]; ++i)
559+ if (!g_strcmp0(strv[i], "HubIncoming") && (i>0))
560+ set_peer_name(strv[i-1]);
561+ g_strfreev(strv);
562+ }
563+
564+ void set_peer_name(const char* peer_name)
565+ {
566+ g_return_if_fail(peer_name && *peer_name);
567+
568+ g_debug("changing '%s' peer_name to '%s'", m_ccad_path.c_str(), peer_name);
569+ m_peer_name = peer_name;
570+
571+ /* If we can find a click icon for the peer,
572+ Use it as the transfer's icon */
573+
574+ GError* error = nullptr;
575+ auto user = click_user_new_for_user(nullptr, nullptr, &error);
576+ if (user != nullptr)
577+ {
578+ gchar* path = click_user_get_path(user, peer_name, &error);
579+ if (path != nullptr)
580+ {
581+ auto manifest = click_user_get_manifest(user, peer_name, &error);
582+ if (manifest != nullptr)
583+ {
584+ const auto icon_name = json_object_get_string_member(manifest, "icon");
585+ if (icon_name != nullptr)
586+ {
587+ auto filename = g_build_filename(path, icon_name, nullptr);
588+ set_icon(filename);
589+ g_free(filename);
590+ }
591+ }
592+ g_free(path);
593+ }
594+ }
595+
596+ if (error != nullptr)
597+ g_warning("Unable to get manifest for '%s' package: %s", peer_name, error->message);
598+
599+ g_clear_object(&user);
600+ g_clear_error(&error);
601+ }
602+
603+ void set_icon(const char* filename)
604+ {
605+ const std::string tmp = filename ? filename : "";
606+ if (app_icon != tmp)
607+ {
608+ g_debug("changing '%s' icon to '%s'", m_ccad_path.c_str(), tmp.c_str());
609+ app_icon = tmp;
610+ emit_changed();
611+ }
612+ }
613+
614+ /***
615+ **** Content Hub
616+ ***/
617+
618+ void get_cucdt_properties()
619+ {
620+ const auto bus_name = CH_BUS_NAME;
621+ const auto object_path = m_cucdt_path.c_str();
622+ const auto interface_name = CH_TRANSFER_IFACE_NAME;
623+
624+ g_dbus_connection_call(m_bus, bus_name, object_path, interface_name,
625+ "Store", nullptr, G_VARIANT_TYPE("(s)"),
626+ G_DBUS_CALL_FLAGS_NONE, -1,
627+ m_cancellable, on_cucdt_store, this);
628+ }
629+
630+ static void on_cucdt_store(GObject* source, GAsyncResult* res, gpointer gself)
631+ {
632+ auto v = connection_call_finish(source, res, "Unable to get store");
633+ if (v != nullptr)
634+ {
635+ const gchar* store = nullptr;
636+ g_variant_get_child(v, 0, "&s", &store);
637+ if (store != nullptr)
638+ static_cast<DBusTransfer*>(gself)->set_store(store);
639+ g_variant_unref(v);
640+ }
641+ }
642+
643+ /***
644+ **** DownloadManager
645+ ***/
646+
647+ void get_ccad_properties()
648+ {
649+ const auto bus_name = DM_BUS_NAME;
650+ const auto object_path = m_ccad_path.c_str();
651+ const auto interface_name = DM_DOWNLOAD_IFACE_NAME;
652
653 g_dbus_connection_call(m_bus, bus_name, object_path, interface_name,
654 "totalSize", nullptr, G_VARIANT_TYPE("(t)"),
655 G_DBUS_CALL_FLAGS_NONE, -1,
656- m_cancellable, on_total_size, this);
657+ m_cancellable, on_ccad_total_size, this);
658
659 g_dbus_connection_call(m_bus, bus_name, object_path, interface_name,
660 "progress", nullptr, G_VARIANT_TYPE("(t)"),
661 G_DBUS_CALL_FLAGS_NONE, -1,
662- m_cancellable, on_progress, this);
663-
664- g_dbus_connection_call(m_bus, bus_name, object_path, interface_name,
665- "metadata", nullptr, G_VARIANT_TYPE("(a{sv})"),
666- G_DBUS_CALL_FLAGS_NONE, -1,
667- m_cancellable, on_metadata, this);
668+ m_cancellable, on_ccad_progress, this);
669 }
670
671- static void on_total_size(GObject* connection,
672- GAsyncResult* res,
673- gpointer gself)
674+ static void on_ccad_total_size(GObject * source,
675+ GAsyncResult * res,
676+ gpointer gself)
677 {
678- GError* error = nullptr;
679-
680- auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection),
681- res,
682- &error);
683-
684- if (error != nullptr)
685- {
686- if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
687- g_warning("Couldn't get download total size: %s", error->message);
688-
689- g_error_free(error);
690- }
691- else
692+ auto v = connection_call_finish(source, res, "Error calling totalSize()");
693+ if (v != nullptr)
694 {
695 guint64 n = 0;
696 g_variant_get_child(v, 0, "t", &n);
697@@ -399,23 +531,12 @@
698 }
699 }
700
701- static void on_progress(GObject* connection,
702- GAsyncResult* res,
703- gpointer gself)
704+ static void on_ccad_progress(GObject * source,
705+ GAsyncResult * res,
706+ gpointer gself)
707 {
708- GError* error = nullptr;
709- auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection),
710- res,
711- &error);
712-
713- if (error != nullptr)
714- {
715- if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
716- g_warning("Couldn't get download progress: %s", error->message);
717-
718- g_error_free(error);
719- }
720- else
721+ auto v = connection_call_finish(source, res, "Error calling progress()");
722+ if (v != nullptr)
723 {
724 guint64 n = 0;
725 g_variant_get_child(v, 0, "t", &n);
726@@ -427,31 +548,63 @@
727 }
728 }
729
730- static void on_metadata(GObject* connection,
731- GAsyncResult* res,
732- gpointer)// gself)
733+ void call_ccad_method_no_args_no_response(const char* method_name)
734+ {
735+ const auto bus_name = DM_BUS_NAME;
736+ const auto object_path = m_ccad_path.c_str();
737+ const auto interface_name = DM_DOWNLOAD_IFACE_NAME;
738+
739+ g_debug("%s transfer %s calling '%s'", G_STRLOC, id.c_str(), method_name);
740+
741+ g_dbus_connection_call(m_bus, bus_name, object_path, interface_name,
742+ method_name, nullptr, nullptr,
743+ G_DBUS_CALL_FLAGS_NONE, -1,
744+ m_cancellable, nullptr, nullptr);
745+ }
746+
747+ /***
748+ ****
749+ ***/
750+
751+ static GVariant* connection_call_finish(GObject * connection,
752+ GAsyncResult * res,
753+ const char * warning)
754 {
755 GError* error = nullptr;
756+
757 auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection),
758 res,
759 &error);
760
761- if (error != nullptr)
762+ if (v == nullptr)
763 {
764 if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
765- g_warning("Couldn't get download metadata: %s", error->message);
766+ g_warning("%s: %s", warning, error->message);
767
768 g_error_free(error);
769 }
770- else
771- {
772- char * variant_str = g_variant_print(v, true);
773- // FIXME: pull appname (and icon) and title from metadata?
774- g_warning("%s unhandled metadata: %s", G_STRFUNC, variant_str);
775- g_free(variant_str);
776- g_variant_unref(v);
777- }
778+
779+ return v;
780 }
781+
782+ core::Signal<> m_changed;
783+
784+ uint64_t m_received = 0;
785+ uint64_t m_total_size = 0;
786+ struct DownloadProgress {
787+ int64_t time_usec; // microseconds since epoch
788+ uint64_t bytes;
789+ DownloadProgress operator-(const DownloadProgress& that) {
790+ return DownloadProgress{time_usec-that.time_usec, bytes-that.bytes};
791+ }
792+ };
793+ std::vector<DownloadProgress> m_history;
794+
795+ GDBusConnection* m_bus = nullptr;
796+ GCancellable* m_cancellable = nullptr;
797+ const std::string m_ccad_path;
798+ const std::string m_cucdt_path;
799+ std::string m_peer_name;
800 };
801
802 } // anonymous namespace
803@@ -460,215 +613,305 @@
804 ****
805 ***/
806
807-DBusWorld::DBusWorld(const std::shared_ptr<MutableModel>& model):
808+class DBusWorld::Impl
809+{
810+public:
811+
812+ Impl(const std::shared_ptr<MutableModel>& model):
813 m_cancellable(g_cancellable_new()),
814 m_model(model)
815-{
816+ {
817 g_bus_get(G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready, this);
818-}
819-
820-DBusWorld::~DBusWorld()
821-{
822+
823+ m_model->removed().connect([this](const Transfer::Id& id){
824+ auto transfer = find_transfer_by_id(id);
825+ if (transfer)
826+ m_removed_ccad.insert(transfer->ccad_path());
827+ });
828+ }
829+
830+ ~Impl()
831+ {
832 g_cancellable_cancel(m_cancellable);
833 g_clear_object(&m_cancellable);
834 set_bus(nullptr);
835 g_clear_object(&m_bus);
836-}
837-
838-void DBusWorld::on_bus_ready(GObject* /*source_object*/,
839- GAsyncResult* res,
840- gpointer gself)
841-{
842- GError* error = nullptr;
843- auto bus = g_bus_get_finish(res, &error);
844-
845- if (error != nullptr)
846- {
847- if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
848- g_warning("Could not get session bus: %s", error->message);
849-
850- g_error_free(error);
851- }
852- else
853- {
854- static_cast<DBusWorld*>(gself)->set_bus(bus);
855- g_object_unref(bus);
856- }
857-}
858-
859-void DBusWorld::set_bus(GDBusConnection* bus)
860-{
861- if (m_bus != nullptr)
862- {
863- for (const auto& tag : m_signal_subscriptions)
864- g_dbus_connection_signal_unsubscribe(m_bus, tag);
865-
866- m_signal_subscriptions.clear();
867- g_clear_object(&m_bus);
868- }
869-
870- if (bus != nullptr)
871- {
872- g_debug("%s: %s", G_STRFUNC, g_dbus_connection_get_unique_name(bus));
873- m_bus = G_DBUS_CONNECTION(g_object_ref(bus));
874-
875- guint tag;
876- tag = g_dbus_connection_signal_subscribe(bus,
877- BUS_NAME,
878- DOWNLOAD_MANAGER_IFACE_NAME,
879- "downloadCreated",
880- "/",
881- nullptr,
882- G_DBUS_SIGNAL_FLAGS_NONE,
883- on_download_created,
884- this,
885- nullptr);
886- m_signal_subscriptions.insert(tag);
887-
888- tag = g_dbus_connection_signal_subscribe (bus,
889- BUS_NAME,
890- DOWNLOAD_IFACE_NAME,
891- nullptr,
892- nullptr,
893- nullptr,
894- G_DBUS_SIGNAL_FLAGS_NONE,
895- on_download_signal,
896- this,
897- nullptr);
898- m_signal_subscriptions.insert(tag);
899- }
900-}
901-
902-namespace
903-{
904- std::shared_ptr<DBusTransfer>
905- find_dbus_transfer_for_object_path(const std::shared_ptr<Model>& model,
906- const std::string& object_path)
907- {
908- std::shared_ptr<DBusTransfer> dbus_transfer;
909-
910- for (const auto& transfer : model->get_all())
911- {
912- const auto tmp = std::static_pointer_cast<DBusTransfer>(transfer);
913-
914- if (tmp && (tmp->object_path()==object_path))
915- {
916- dbus_transfer = tmp;
917- break;
918- }
919- }
920-
921- return dbus_transfer;
922- }
923-}
924-
925-void DBusWorld::on_download_signal(GDBusConnection*, //connection,
926- const gchar*, //sender_name,
927- const gchar* object_path,
928- const gchar*, //interface_name,
929- const gchar* signal_name,
930- GVariant* parameters,
931- gpointer gself)
932-{
933- auto self = static_cast<DBusWorld*>(gself);
934-
935- auto dbus_transfer = find_dbus_transfer_for_object_path(self->m_model, object_path);
936-
937- if (!dbus_transfer)
938- {
939- g_message("A %s that we didn't know about just emitted signal '%s' -- "
940- "might be a transfer that was here before us?",
941- DOWNLOAD_IFACE_NAME, signal_name);
942- self->add_transfer(object_path);
943- dbus_transfer = find_dbus_transfer_for_object_path(self->m_model, object_path);
944- g_return_if_fail (dbus_transfer);
945- }
946-
947- dbus_transfer->handle_signal(signal_name, parameters);
948-}
949-
950-void DBusWorld::on_download_created(GDBusConnection*, //connection,
951- const gchar*, //sender_name,
952- const gchar*, //object_path,
953- const gchar*, //interface_name,
954- const gchar*, //signal_name,
955- GVariant* parameters,
956- gpointer gself)
957-{
958- gchar* download_path = nullptr;
959- g_variant_get_child(parameters, 0, "o", &download_path);
960-
961- if (download_path != nullptr)
962- {
963- if (g_variant_is_object_path(download_path))
964- static_cast<DBusWorld*>(gself)->add_transfer(download_path);
965-
966- g_free(download_path);
967- }
968-}
969-
970-void DBusWorld::add_transfer(const char* object_path)
971-{
972- // create a new Transfer and pass it to the model
973- auto dbus_transfer = new DBusTransfer(m_bus, object_path);
974- std::shared_ptr<Transfer> transfer(dbus_transfer);
975- m_model->add(transfer);
976-
977- // notify the model whenever the Transfer changes
978- const auto id = dbus_transfer->id;
979- dbus_transfer->changed().connect([this,id]{
980- if (m_model->get(id))
981- m_model->emit_changed(id);
982- });
983-}
984-
985-namespace
986-{
987- std::shared_ptr<DBusTransfer>
988- get_dbus_transfer(const std::shared_ptr<Model>& model, const Transfer::Id& id)
989- {
990- auto transfer = model->get(id);
991+ }
992+
993+ void start(const Transfer::Id& id)
994+ {
995+ auto transfer = find_transfer_by_id(id);
996+ g_return_if_fail(transfer);
997+ transfer->start();
998+ }
999+
1000+ void pause(const Transfer::Id& id)
1001+ {
1002+ auto transfer = find_transfer_by_id(id);
1003+ g_return_if_fail(transfer);
1004+ transfer->pause();
1005+ }
1006+
1007+ void resume(const Transfer::Id& id)
1008+ {
1009+ auto transfer = find_transfer_by_id(id);
1010+ g_return_if_fail(transfer);
1011+ transfer->resume();
1012+ }
1013+
1014+ void cancel(const Transfer::Id& id)
1015+ {
1016+ auto transfer = find_transfer_by_id(id);
1017+ g_return_if_fail(transfer);
1018+ transfer->cancel();
1019+ }
1020+
1021+ void open(const Transfer::Id& id)
1022+ {
1023+ auto transfer = find_transfer_by_id(id);
1024+ g_return_if_fail(transfer);
1025+ transfer->open();
1026+ transfer->open_app();
1027+ }
1028+
1029+ void open_app(const Transfer::Id& id)
1030+ {
1031+ auto transfer = find_transfer_by_id(id);
1032+ g_return_if_fail(transfer);
1033+ transfer->open_app();
1034+ }
1035+
1036+private:
1037+
1038+ static void on_bus_ready(GObject * /*source_object*/,
1039+ GAsyncResult * res,
1040+ gpointer gself)
1041+ {
1042+ GError* error = nullptr;
1043+ auto bus = g_bus_get_finish(res, &error);
1044+
1045+ if (bus == nullptr)
1046+ {
1047+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1048+ g_warning("Could not get session bus: %s", error->message);
1049+
1050+ g_error_free(error);
1051+ }
1052+ else
1053+ {
1054+ static_cast<Impl*>(gself)->set_bus(bus);
1055+ g_object_unref(bus);
1056+ }
1057+ }
1058+
1059+ void set_bus(GDBusConnection* bus)
1060+ {
1061+ if (m_bus != nullptr)
1062+ {
1063+ for (const auto& tag : m_signal_subscriptions)
1064+ g_dbus_connection_signal_unsubscribe(m_bus, tag);
1065+
1066+ m_signal_subscriptions.clear();
1067+ g_clear_object(&m_bus);
1068+ }
1069+
1070+ if (bus != nullptr)
1071+ {
1072+ g_debug("%s: %s", G_STRFUNC, g_dbus_connection_get_unique_name(bus));
1073+ m_bus = G_DBUS_CONNECTION(g_object_ref(bus));
1074+
1075+ guint tag;
1076+
1077+ tag = g_dbus_connection_signal_subscribe(bus,
1078+ DM_BUS_NAME,
1079+ DM_DOWNLOAD_IFACE_NAME,
1080+ nullptr,
1081+ nullptr,
1082+ nullptr,
1083+ G_DBUS_SIGNAL_FLAGS_NONE,
1084+ on_download_signal,
1085+ this,
1086+ nullptr);
1087+ m_signal_subscriptions.insert(tag);
1088+
1089+ tag = g_dbus_connection_signal_subscribe(bus,
1090+ CH_BUS_NAME,
1091+ CH_TRANSFER_IFACE_NAME,
1092+ nullptr,
1093+ nullptr,
1094+ nullptr,
1095+ G_DBUS_SIGNAL_FLAGS_NONE,
1096+ on_transfer_signal_static,
1097+ this,
1098+ nullptr);
1099+ m_signal_subscriptions.insert(tag);
1100+ }
1101+ }
1102+
1103+ static void on_transfer_signal_static(GDBusConnection* /*connection*/,
1104+ const gchar* /*sender_name*/,
1105+ const gchar* object_path,
1106+ const gchar* /*interface_name*/,
1107+ const gchar* signal_name,
1108+ GVariant* parameters,
1109+ gpointer gself)
1110+ {
1111+ static_cast<Impl*>(gself)->on_transfer_signal(object_path, signal_name, parameters);
1112+ }
1113+
1114+ void on_transfer_signal(const gchar* cucdt_path,
1115+ const gchar* signal_name,
1116+ GVariant* parameters)
1117+ {
1118+ g_debug("transfer signal: %s %s %s", cucdt_path, signal_name, g_variant_print(parameters, TRUE));
1119+
1120+ if (!g_strcmp0(signal_name, "DownloadIdChanged"))
1121+ {
1122+ const char* ccad_path = nullptr;
1123+ g_variant_get_child(parameters, 0, "&s", &ccad_path);
1124+ g_return_if_fail(cucdt_path != nullptr);
1125+
1126+ // ensure this ccad/cucdt pair is tracked
1127+ if (!find_transfer_by_ccad_path(ccad_path))
1128+ create_new_transfer(ccad_path, cucdt_path);
1129+ }
1130+ else
1131+ {
1132+ // Route this signal to the DBusTransfer for processing
1133+ auto transfer = find_transfer_by_cucdt_path(cucdt_path);
1134+ if (transfer)
1135+ transfer->handle_cucdt_signal(signal_name, parameters);
1136+ }
1137+ }
1138+
1139+ static void on_download_signal(GDBusConnection* /*connection*/,
1140+ const gchar* /*sender_name*/,
1141+ const gchar* ccad_path,
1142+ const gchar* /*interface_name*/,
1143+ const gchar* signal_name,
1144+ GVariant* parameters,
1145+ gpointer gself)
1146+ {
1147+ g_debug("download signal: %s %s %s", ccad_path, signal_name, g_variant_print(parameters, TRUE));
1148+
1149+ // Route this signal to the DBusTransfer for processing
1150+ auto self = static_cast<Impl*>(gself);
1151+ auto transfer = self->find_transfer_by_ccad_path(ccad_path);
1152+ if (transfer)
1153+ transfer->handle_ccad_signal(signal_name, parameters);
1154+ }
1155+
1156+ /***
1157+ ****
1158+ ***/
1159+
1160+ std::shared_ptr<DBusTransfer> find_transfer_by_ccad_path(const std::string& path)
1161+ {
1162+ for (const auto& transfer : m_model->get_all())
1163+ {
1164+ const auto tmp = std::static_pointer_cast<DBusTransfer>(transfer);
1165+
1166+ if (tmp && (path == tmp->ccad_path()))
1167+ return tmp;
1168+ }
1169+
1170+ return nullptr;
1171+ }
1172+
1173+ std::shared_ptr<DBusTransfer> find_transfer_by_cucdt_path(const std::string& path)
1174+ {
1175+ for (const auto& transfer : m_model->get_all())
1176+ {
1177+ const auto tmp = std::static_pointer_cast<DBusTransfer>(transfer);
1178+
1179+ if (tmp && (path == tmp->cucdt_path()))
1180+ return tmp;
1181+ }
1182+
1183+ return nullptr;
1184+ }
1185+
1186+ void create_new_transfer(const std::string& ccad_path,
1187+ const std::string& cucdt_path)
1188+ {
1189+ // don't let transfers reappear after they've been cleared by the user
1190+ if (m_removed_ccad.count(ccad_path))
1191+ return;
1192+
1193+ auto new_transfer = std::make_shared<DBusTransfer>(m_bus, ccad_path, cucdt_path);
1194+
1195+ m_model->add(new_transfer);
1196+
1197+ // when one of the DBusTransfer's properties changes,
1198+ // emit a change signal for the model
1199+ const auto id = new_transfer->id;
1200+ new_transfer->changed().connect([this,id]{
1201+ if (m_model->get(id))
1202+ m_model->emit_changed(id);
1203+ });
1204+ }
1205+
1206+ std::shared_ptr<DBusTransfer> find_transfer_by_id(const Transfer::Id& id)
1207+ {
1208+ auto transfer = m_model->get(id);
1209 g_return_val_if_fail(transfer, std::shared_ptr<DBusTransfer>());
1210 return std::static_pointer_cast<DBusTransfer>(transfer);
1211 }
1212-}
1213-
1214-void DBusWorld::start(const Transfer::Id& id)
1215-{
1216- auto dbus_transfer = get_dbus_transfer (m_model, id);
1217- g_return_if_fail(dbus_transfer);
1218- dbus_transfer->start();
1219-}
1220-
1221-void DBusWorld::pause(const Transfer::Id& id)
1222-{
1223- auto dbus_transfer = get_dbus_transfer (m_model, id);
1224- g_return_if_fail(dbus_transfer);
1225- dbus_transfer->pause();
1226-}
1227-
1228-void DBusWorld::resume(const Transfer::Id& id)
1229-{
1230- auto dbus_transfer = get_dbus_transfer (m_model, id);
1231- g_return_if_fail(dbus_transfer);
1232- dbus_transfer->resume();
1233-}
1234-
1235-void DBusWorld::cancel(const Transfer::Id& id)
1236-{
1237- auto dbus_transfer = get_dbus_transfer (m_model, id);
1238- g_return_if_fail(dbus_transfer);
1239- dbus_transfer->cancel();
1240-}
1241-
1242-void DBusWorld::open(const Transfer::Id& id)
1243-{
1244- std::cerr << G_STRFUNC << " FIXME " << id << std::endl;
1245-}
1246-
1247-void DBusWorld::open_app(const Transfer::Id& id)
1248-{
1249- std::cerr << G_STRFUNC << " FIXME " << id << std::endl;
1250+
1251+ GDBusConnection* m_bus = nullptr;
1252+ GCancellable* m_cancellable = nullptr;
1253+ std::set<guint> m_signal_subscriptions;
1254+ std::shared_ptr<MutableModel> m_model;
1255+ std::set<std::string> m_removed_ccad;
1256+};
1257+
1258+/***
1259+****
1260+***/
1261+
1262+DBusWorld::DBusWorld(const std::shared_ptr<MutableModel>& model):
1263+ impl(new Impl(model))
1264+{
1265+}
1266+
1267+DBusWorld::~DBusWorld()
1268+{
1269+}
1270+
1271+void
1272+DBusWorld::open(const Transfer::Id& id)
1273+{
1274+ impl->open(id);
1275+}
1276+
1277+void
1278+DBusWorld::start(const Transfer::Id& id)
1279+{
1280+ impl->start(id);
1281+}
1282+
1283+void
1284+DBusWorld::pause(const Transfer::Id& id)
1285+{
1286+ impl->pause(id);
1287+}
1288+
1289+void
1290+DBusWorld::resume(const Transfer::Id& id)
1291+{
1292+ impl->resume(id);
1293+}
1294+
1295+void
1296+DBusWorld::cancel(const Transfer::Id& id)
1297+{
1298+ impl->cancel(id);
1299+}
1300+
1301+void
1302+DBusWorld::open_app(const Transfer::Id& id)
1303+{
1304+ impl->open_app(id);
1305 }
1306
1307 /***
1308
1309=== modified file 'tests/manual'
1310--- tests/manual 2014-06-18 15:26:37 +0000
1311+++ tests/manual 2014-08-26 20:52:17 +0000
1312@@ -1,23 +1,23 @@
1313
1314-Test-case indicator-transfer/simple-download-check
1315+Test-case indicator-transfer/download-an-image
1316 <dl>
1317 <dt>Ensure indicator-transfer-service is running<dt>
1318- <dd>The indicator should be visible in the panel</dd>
1319-
1320- <dt>Run the script tests/simple-download.py and, immediately afterwards,
1321- click or pull down on the indicator so that its menu is visible
1322- and its menuitems can be observed while the downloads run.</dt>
1323- <dd>In the 'Ongoing Transfers' section, the transfers should appear</dd>
1324- <dd>In the 'Ongoing Transfers' section, a 'Pause all' button should appear</dd>
1325- <dd>While transfers are active, the indicator's icon should indicate activity</dd>
1326- <dd>As each transfer finishes, its menuitem should move from the 'Ongoing' to 'Successful' section</dd>
1327- <dd>As the first transfer is moved to the 'Successful' section, a 'Clear all' button should appear</dd>
1328- <dd>As the last transfer finishes, the indicator's icon should change to indicate an idle state</dd>
1329- <dd>As the last transfer finishes, the 'Pause all' button should disappear</dd>
1330-
1331- <dt>After all the transfers finish, press the 'Clear all' button.</dt>
1332- <dd>All three transfers should be cleared from the menu.</dd>
1333- <dd>The 'Clear all' button should disappear</dd>
1334+ <dd>The indicator should be visible in the panel</dd>
1335+ <dt>Start downloading an image from the Browser to the Gallery.
1336+ (In the Browser, tap-and-hold on an image, choose 'Save image'
1337+ in the popup, and select 'Gallery' at the 'Open with ' prompt.</dt>
1338+ <dd>In the 'Ongoing Transfers' section, the transfer should appear</dd>
1339+ <dd>In the 'Ongoing Transfers' section, a 'Pause all' button should appear</dd>
1340+ <dd>While transfer is active, the indicator's icon should indicate activity</dd>
1341+ <dd>While transfer is active, the menuitem's progressbar should show its progress</dd>
1342+ <dd>As the transfer finishes, its menuitem should move from the 'Ongoing' to 'Successful' section</dd>
1343+ <dd>As the transfer finishes, the indicator's icon should change to indicate an idle state</dd>
1344+ <dd>As the transfer finishes, the 'Pause all' button should disappear</dd>
1345+ <dt>Click on the completed transfer's menuitem</dt>
1346+ <dd>The gallery app should launch</dd>
1347+ <dt>Press the 'Clear all' button.</dt>
1348+ <dd>The transfer should be cleared from the menu.</dd>
1349+ <dd>The 'Clear all' button should disappear</dd>
1350 </dt>
1351
1352 <strong>

Subscribers

People subscribed via source and target branches