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