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 | 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> |
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://