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

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

Commit message

Better use of information provided by content-hub.

Description of the change

=== Description of the Change

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

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

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

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

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

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

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

=== Merge Proposal Checklist

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

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

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

Yes

> Did the code build without warnings?

Yes

> Did the tests run successfully?

Yes

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

Yes, utopic image 210 on a mako device

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

Yes

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

indicator-transfer/simple-download-check

When testing, please note the comment above about rendering.

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

Few comments.

review: Needs Fixing
19. By Charles Kerr

simplify the download speed estimation.

20. By Charles Kerr

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

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

Great!

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2014-06-17 01:36:16 +0000
+++ CMakeLists.txt 2014-08-26 20:52:17 +0000
@@ -31,11 +31,14 @@
31find_package (PkgConfig REQUIRED)31find_package (PkgConfig REQUIRED)
32include (FindPkgConfig)32include (FindPkgConfig)
3333
34pkg_check_modules (SERVICE_DEPS REQUIRED34pkg_check_modules(SERVICE_DEPS REQUIRED
35 glib-2.0>=2.3635 glib-2.0>=2.36
36 gio-unix-2.0>=2.3636 gio-unix-2.0>=2.36
37 properties-cpp>=0.0.1)37 properties-cpp>=0.0.1
38include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})38 click-0.4>=0.4.30
39 json-glib-1.0
40 ubuntu-app-launch-2)
41include_directories(SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
3942
40##43##
41## custom targets44## custom targets
4245
=== modified file 'debian/control'
--- debian/control 2014-06-25 15:34:10 +0000
+++ debian/control 2014-08-26 20:52:17 +0000
@@ -8,6 +8,9 @@
8 g++-4.9,8 g++-4.9,
9 libglib2.0-dev (>= 2.35.4),9 libglib2.0-dev (>= 2.35.4),
10 libproperties-cpp-dev,10 libproperties-cpp-dev,
11 libclick-0.4-dev (>= 0.4.30),
12 libjson-glib-dev,
13 libubuntu-app-launch2-dev,
11# for coverage reports14# for coverage reports
12 lcov,15 lcov,
13# for tests16# for tests
1417
=== modified file 'include/transfer/transfer.h'
--- include/transfer/transfer.h 2014-06-25 22:48:12 +0000
+++ include/transfer/transfer.h 2014-08-26 20:52:17 +0000
@@ -56,7 +56,8 @@
56 // [0...1]56 // [0...1]
57 float progress = 0.0;57 float progress = 0.0;
5858
59 uint64_t speed_bps = 0;59 // bytes per second
60 uint64_t speed_Bps = 0;
6061
61 uint64_t total_size = 0;62 uint64_t total_size = 0;
6263
6364
=== modified file 'include/transfer/world-dbus.h'
--- include/transfer/world-dbus.h 2014-06-17 01:36:16 +0000
+++ include/transfer/world-dbus.h 2014-08-26 20:52:17 +0000
@@ -47,19 +47,8 @@
47 void open_app(const Transfer::Id& id);47 void open_app(const Transfer::Id& id);
4848
49private:49private:
50 GDBusConnection* m_bus = nullptr;50 class Impl;
51 GCancellable* m_cancellable = nullptr;51 std::unique_ptr<Impl> impl;
52 std::set<guint> m_signal_subscriptions;
53 std::shared_ptr<MutableModel> m_model;
54
55 void set_bus(GDBusConnection*);
56 void add_transfer(const char* object_path);
57
58 static void on_bus_ready(GObject*, GAsyncResult*, gpointer);
59 static void on_progress(GObject*, GAsyncResult*, gpointer);
60 static void on_total_size(GObject*, GAsyncResult*, gpointer);
61 static void on_download_signal(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant*, gpointer);
62 static void on_download_created(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant*, gpointer);
63};52};
6453
65} // namespace transfer54} // namespace transfer
6655
=== modified file 'src/view-console.cpp'
--- src/view-console.cpp 2014-06-17 01:36:16 +0000
+++ src/view-console.cpp 2014-08-26 20:52:17 +0000
@@ -56,7 +56,7 @@
56 transfer->app_icon.c_str(),56 transfer->app_icon.c_str(),
57 (size_t)transfer->time_started,57 (size_t)transfer->time_started,
58 transfer->seconds_left,58 transfer->seconds_left,
59 transfer->speed_bps/1024.0,59 transfer->speed_Bps/1024.0,
60 transfer->progress,60 transfer->progress,
61 transfer->error_string.c_str(),61 transfer->error_string.c_str(),
62 transfer->local_path.c_str());62 transfer->local_path.c_str());
6363
=== modified file 'src/view-gmenu.cpp'
--- src/view-gmenu.cpp 2014-07-09 21:18:50 +0000
+++ src/view-gmenu.cpp 2014-08-26 20:52:17 +0000
@@ -152,8 +152,9 @@
152 if (transfer)152 if (transfer)
153 {153 {
154 g_variant_builder_add(&b, "{sv}", "percent",154 g_variant_builder_add(&b, "{sv}", "percent",
155 g_variant_new_double(transfer->progress));155 g_variant_new_double(CLAMP(transfer->progress, 0.0, 1.0)));
156 if (transfer->seconds_left >= 0)156
157 if ((transfer->seconds_left >= 0) && (int(transfer->progress*100.0) < 100))
157 {158 {
158 g_variant_builder_add(&b, "{sv}", "seconds-left",159 g_variant_builder_add(&b, "{sv}", "seconds-left",
159 g_variant_new_int32(transfer->seconds_left));160 g_variant_new_int32(transfer->seconds_left));
@@ -612,11 +613,23 @@
612 g_menu_item_set_attribute (menu_item, ATTRIBUTE_X_TYPE,613 g_menu_item_set_attribute (menu_item, ATTRIBUTE_X_TYPE,
613 "s", "com.canonical.indicator.transfer");614 "s", "com.canonical.indicator.transfer");
614615
615 //FIXME: this is a placeholder616 GVariant * serialized_icon = nullptr;
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))
617 auto v = g_icon_serialize(icon);618 {
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());
619 g_object_unref(icon);620 auto icon = g_file_icon_new(file);
621 serialized_icon = g_icon_serialize(icon);
622 g_clear_object(&icon);
623 g_clear_object(&file);
624 }
625 if (serialized_icon == nullptr)
626 {
627 auto icon = g_themed_icon_new("image-missing");
628 serialized_icon = g_icon_serialize(icon);
629 g_clear_object(&icon);
630 }
631 g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ICON, serialized_icon);
632 g_variant_unref(serialized_icon);
620633
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);
622 g_menu_item_set_action_and_target_value (menu_item,635 g_menu_item_set_action_and_target_value (menu_item,
623636
=== modified file 'src/world-dbus.cpp'
--- src/world-dbus.cpp 2014-07-09 22:33:01 +0000
+++ src/world-dbus.cpp 2014-08-26 20:52:17 +0000
@@ -19,6 +19,12 @@
1919
20#include <transfer/world-dbus.h>20#include <transfer/world-dbus.h>
2121
22#include <click.h>
23#include <ubuntu-app-launch.h>
24
25#include <json-glib/json-glib.h>
26
27#include <algorithm>
22#include <iostream>28#include <iostream>
2329
24namespace unity {30namespace unity {
@@ -27,25 +33,41 @@
2733
28namespace {34namespace {
2935
30#define BUS_NAME "com.canonical.applications.Downloader"36static constexpr char const * DM_BUS_NAME {"com.canonical.applications.Downloader"};
31#define DOWNLOAD_MANAGER_IFACE_NAME "com.canonical.applications.DownloadManager"37static constexpr char const * DM_MANAGER_IFACE_NAME {"com.canonical.applications.DownloadManager"};
32#define DOWNLOAD_IFACE_NAME "com.canonical.applications.Download"38static constexpr char const * DM_DOWNLOAD_IFACE_NAME {"com.canonical.applications.Download"};
3339
40static constexpr char const * CH_BUS_NAME {"com.ubuntu.content.dbus.Service"};
41static constexpr char const * CH_TRANSFER_IFACE_NAME {"com.ubuntu.content.dbus.Transfer"};
42
43
44/**
45 * A Transfer whose state comes from content-hub and ubuntu-download-manager.
46 *
47 * Each DBusTransfer tracks a com.canonical.applications.Download (ccad) object
48 * from ubuntu-download-manager. The ccad is used for pause/resume/cancel,
49 * state change / download progress signals, etc.
50 *
51 * Each DBusTransfer also tracks a com.ubuntu.content.dbus.Transfer (cucdt)
52 * object from content-hub. The cucdt is used for learning the download's peer
53 * and for calling Charge() to launch the peer's app.
54 */
34class DBusTransfer: public Transfer55class DBusTransfer: public Transfer
35{56{
36public:57public:
3758
38 DBusTransfer(GDBusConnection* connection, const char* object_path):59 DBusTransfer(GDBusConnection* connection,
60 const std::string& ccad_path,
61 const std::string& cucdt_path):
39 m_bus(G_DBUS_CONNECTION(g_object_ref(connection))),62 m_bus(G_DBUS_CONNECTION(g_object_ref(connection))),
40 m_cancellable(g_cancellable_new()),63 m_cancellable(g_cancellable_new()),
41 m_object_path(object_path)64 m_ccad_path(ccad_path),
65 m_cucdt_path(cucdt_path)
42 {66 {
43 g_debug("creating a new DBusTransfer for '%s'", object_path);
44
45 id = next_unique_id();67 id = next_unique_id();
46 time_started = time(nullptr);68 time_started = time(nullptr);
4769 get_ccad_properties();
48 get_properties_from_bus();70 get_cucdt_properties();
49 }71 }
5072
51 ~DBusTransfer()73 ~DBusTransfer()
@@ -59,39 +81,82 @@
5981
60 void start()82 void start()
61 {83 {
62 g_return_if_fail (can_start());84 g_return_if_fail(can_start());
63 85
64 call_method_no_args_no_response ("start");86 call_ccad_method_no_args_no_response("start");
65 }87 }
6688
67 void pause()89 void pause()
68 {90 {
69 g_return_if_fail (can_pause());91 g_return_if_fail(can_pause());
70 92
71 call_method_no_args_no_response("pause");93 call_ccad_method_no_args_no_response("pause");
72 }94 }
7395
74 void resume()96 void resume()
75 {97 {
76 g_return_if_fail(can_resume());98 g_return_if_fail(can_resume());
7799
78 call_method_no_args_no_response("resume");100 call_ccad_method_no_args_no_response("resume");
79 }101 }
80102
81 void cancel()103 void cancel()
82 {104 {
83 call_method_no_args_no_response("cancel");105 call_ccad_method_no_args_no_response("cancel");
84 }106 }
85107
86 const std::string& object_path() const108 void open()
87 {109 {
88 return m_object_path;110 /* Once a transfer is complete, an app can be launched to process
89 }111 it by calling Charge() with an empty variant list argument */
90112 g_return_if_fail(!m_cucdt_path.empty());
91 void handle_signal(const gchar* signal_name, GVariant* parameters)113 g_dbus_connection_call(m_bus,
92 {114 CH_BUS_NAME,
93 g_debug ("%s transfer %s got signal '%s'", G_STRLOC, id.c_str(), signal_name);115 m_cucdt_path.c_str(),
94116 CH_TRANSFER_IFACE_NAME,
117 "Charge",
118 g_variant_new("(av)", nullptr),
119 nullptr,
120 G_DBUS_CALL_FLAGS_NONE,
121 -1, // default timeout
122 m_cancellable,
123 on_cucdt_charge, // callback
124 nullptr); // callback user_data
125 }
126
127 void open_app()
128 {
129 g_return_if_fail(!m_peer_name.empty());
130
131 gchar* app_id = ubuntu_app_launch_triplet_to_app_id(m_peer_name.c_str(), nullptr, nullptr);
132 g_debug("calling ubuntu_app_launch_start_application() for %s", app_id);
133 ubuntu_app_launch_start_application(app_id, nullptr);
134 g_free(app_id);
135 }
136
137 const std::string& cucdt_path() const
138 {
139 return m_cucdt_path;
140 }
141
142 const std::string& ccad_path() const
143 {
144 return m_ccad_path;
145 }
146
147 void handle_cucdt_signal(const gchar* signal_name, GVariant* parameters)
148 {
149 if (!g_strcmp0(signal_name, "StoreChanged"))
150 {
151 const gchar* uri = nullptr;
152 g_variant_get_child(parameters, 0, "&s", &uri);
153 if (uri != nullptr)
154 set_store(uri);
155 }
156 }
157
158 void handle_ccad_signal(const gchar* signal_name, GVariant* parameters)
159 {
95 if (!g_strcmp0(signal_name, "started"))160 if (!g_strcmp0(signal_name, "started"))
96 {161 {
97 if (get_signal_success_arg(parameters))162 if (get_signal_success_arg(parameters))
@@ -151,7 +216,7 @@
151 int32_t i;216 int32_t i;
152 char* str = nullptr;217 char* str = nullptr;
153 g_variant_get_child(parameters, 0, "(is)", &i, &str);218 g_variant_get_child(parameters, 0, "(is)", &i, &str);
154 g_message("%s setting error to '%s'", G_STRLOC, str);219 g_debug("%s setting error to '%s'", G_STRLOC, str);
155 set_error_string(str);220 set_error_string(str);
156 set_state(ERROR);221 set_state(ERROR);
157 g_free(str);222 g_free(str);
@@ -166,9 +231,19 @@
166231
167private:232private:
168233
169 // the 'started', 'paused', 'resumed', and 'canceled' signals234 static void on_cucdt_charge(GObject * source,
170 // from com.canonical.applications.Download all have a single235 GAsyncResult * res,
171 // parameter, a boolean success flag.236 gpointer /*unused*/)
237 {
238 auto v = connection_call_finish(source, res, "Error calling Charge()");
239 g_clear_pointer(&v, g_variant_unref);
240 }
241
242 void emit_changed() { changed()(); }
243
244 /* The 'started', 'paused', 'resumed', and 'canceled' signals
245 from com.canonical.applications.Download all have a single
246 parameter, a boolean success flag. */
172 bool get_signal_success_arg(GVariant* parameters)247 bool get_signal_success_arg(GVariant* parameters)
173 {248 {
174 gboolean success = false;249 gboolean success = false;
@@ -178,70 +253,32 @@
178 return success;253 return success;
179 }254 }
180255
181 void call_method_no_args_no_response(const char* method_name)256 uint64_t get_averaged_speed_Bps()
182 {257 {
183 g_debug ("%s transfer %s calling '%s'", G_STRLOC, id.c_str(), method_name);258 // limit the average to the last X samples
184
185 g_dbus_connection_call(m_bus, // connection
186 BUS_NAME, // bus_name
187 m_object_path.c_str(), // object path
188 DOWNLOAD_IFACE_NAME, // interface name
189 method_name, // method name
190 nullptr, // parameters
191 nullptr, // reply type
192 G_DBUS_CALL_FLAGS_NONE, // flags
193 -1, // default timeout
194 m_cancellable, // cancellable
195 nullptr, // callback
196 nullptr /*user data*/);
197 }
198
199
200 core::Signal<> m_changed;
201 void emit_changed() { changed()(); }
202
203 uint64_t m_received = 0;
204 uint64_t m_total_size = 0;
205 std::vector<std::pair<GTimeVal,uint64_t>> m_history;
206
207 static double get_time_from_timeval(const GTimeVal& t)
208 {
209 return t.tv_sec + (t.tv_usec / (double)G_USEC_PER_SEC);
210 }
211
212 // gets an averaged speed based on information saved from
213 // recent calls to set_progress()
214 uint64_t get_average_speed_bps()
215 {
216 unsigned int n_samples = 0;
217 uint64_t sum = 0;
218
219 // prune out samples by size
220 static constexpr int max_slots = 50;259 static constexpr int max_slots = 50;
221 if (m_history.size() > max_slots)260 if (m_history.size() > max_slots)
222 m_history.erase(m_history.begin(), m_history.end()-max_slots);261 m_history.erase(m_history.begin(), m_history.end()-max_slots);
223262
224 static constexpr time_t max_age_seconds = 30;263 // limit the average to the last Y seconds
225 GTimeVal now_tv;264 static constexpr unsigned int max_age_seconds = 30;
226 g_get_current_time(&now_tv);265 const auto oldest_allowed_usec = g_get_real_time() - (max_age_seconds * G_USEC_PER_SEC);
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;};
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));
229 {268
230 const double begin_time = get_time_from_timeval(m_history[i-1].first);269 uint64_t Bps;
231 if (now - begin_time > max_age_seconds)270
232 continue;271 if (m_history.size() < 2)
233272 {
234 const double end_time = get_time_from_timeval(m_history[i].first);273 Bps = 0;
235 const double time_diff = end_time - begin_time;274 }
236 const uint64_t received_diff = m_history[i].second -275 else
237 m_history[i-1].second;276 {
238 const uint64_t bps = uint64_t(received_diff / time_diff);277 const auto diff = m_history.back() - m_history.front();
239278 Bps = (diff.bytes * G_USEC_PER_SEC) / diff.time_usec;
240 sum += bps;279 }
241 ++n_samples;280
242 }281 return Bps;
243
244 return n_samples ? sum / n_samples : 0;
245 }282 }
246283
247 void update_progress()284 void update_progress()
@@ -249,27 +286,25 @@
249 auto tmp_total_size = total_size;286 auto tmp_total_size = total_size;
250 auto tmp_progress = progress;287 auto tmp_progress = progress;
251 auto tmp_seconds_left = seconds_left;288 auto tmp_seconds_left = seconds_left;
252 auto tmp_speed_bps = speed_bps;289 auto tmp_speed_Bps = speed_Bps;
253290
254 if (m_total_size && m_received)291 if (m_total_size && m_received)
255 {292 {
256 // update our speed tables293 // update our speed tables
257 GTimeVal now;294 m_history.push_back(DownloadProgress{g_get_real_time(),m_received});
258 g_get_current_time(&now);
259 m_history.push_back(std::pair<GTimeVal,uint64_t>(now, m_received));
260295
261 const auto bps = get_average_speed_bps();296 const auto Bps = get_averaged_speed_Bps();
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;
263298
264 tmp_total_size = m_total_size;299 tmp_total_size = m_total_size;
265 tmp_speed_bps = bps;300 tmp_speed_Bps = Bps;
266 tmp_progress = m_received / (float)m_total_size;301 tmp_progress = m_received / (float)m_total_size;
267 tmp_seconds_left = seconds;302 tmp_seconds_left = seconds;
268 }303 }
269 else304 else
270 {305 {
271 tmp_total_size = 0;306 tmp_total_size = 0;
272 tmp_speed_bps = 0;307 tmp_speed_Bps = 0;
273 tmp_progress = 0.0;308 tmp_progress = 0.0;
274 tmp_seconds_left = -1;309 tmp_seconds_left = -1;
275 }310 }
@@ -284,13 +319,14 @@
284319
285 if (seconds_left != tmp_seconds_left)320 if (seconds_left != tmp_seconds_left)
286 {321 {
322 g_debug("changing '%s' seconds_left to '%d'", m_ccad_path.c_str(), (int)tmp_seconds_left);
287 seconds_left = tmp_seconds_left;323 seconds_left = tmp_seconds_left;
288 changed = true;324 changed = true;
289 }325 }
290326
291 if (speed_bps != tmp_speed_bps)327 if (speed_Bps != tmp_speed_Bps)
292 {328 {
293 speed_bps = tmp_speed_bps;329 speed_Bps = tmp_speed_Bps;
294 changed = true;330 changed = true;
295 }331 }
296332
@@ -312,7 +348,7 @@
312348
313 if (!can_pause())349 if (!can_pause())
314 {350 {
315 speed_bps = 0;351 speed_Bps = 0;
316 m_history.clear();352 m_history.clear();
317 }353 }
318 354
@@ -325,6 +361,7 @@
325 const std::string tmp = str ? str : "";361 const std::string tmp = str ? str : "";
326 if (error_string != tmp)362 if (error_string != tmp)
327 {363 {
364 g_debug("changing '%s' error to '%s'", m_ccad_path.c_str(), tmp.c_str());
328 error_string = tmp;365 error_string = tmp;
329 emit_changed();366 emit_changed();
330 }367 }
@@ -334,60 +371,155 @@
334 {371 {
335 const std::string tmp = str ? str : "";372 const std::string tmp = str ? str : "";
336 if (local_path != tmp)373 if (local_path != tmp)
337 {374 {
338 local_path = tmp;375 g_debug("changing '%s' path to '%s'", m_ccad_path.c_str(), tmp.c_str());
339 emit_changed();376 local_path = tmp;
340 }377 emit_changed();
341 }378 }
342379
343 /***380 // If we don't already have a title,
344 **** DBUS381 // use the file's basename as the title
345 ***/382 if (title.empty())
346383 {
347 GDBusConnection* m_bus = nullptr;384 auto bname = g_path_get_basename(str);
348 GCancellable* m_cancellable = nullptr;385 set_title(bname);
349 const std::string m_object_path;386 g_free(bname);
350387 }
351 void get_properties_from_bus()388 }
352 {389
353 const auto bus_name = BUS_NAME;390 void set_title(const char* title_in)
354 const auto object_path = m_object_path.c_str();391 {
355 const auto interface_name = DOWNLOAD_IFACE_NAME;392 const std::string tmp = title_in ? title_in : "";
393 if (title != tmp)
394 {
395 g_debug("changing '%s' title to '%s'", m_ccad_path.c_str(), tmp.c_str());
396 title = tmp;
397 emit_changed();
398 }
399 }
400
401 void set_store(const char* store)
402 {
403 /**
404 * This is a workaround until content-hub exposes a peer getter.
405 * As an interim step, this code sniffs the peer by looking at the store.
406 * the peer's listed in the directory component before HubIncoming, e.g.:
407 * "/home/phablet/.cache/com.ubuntu.gallery/HubIncoming/4"
408 */
409 char** strv = g_strsplit(store, "/", -1);
410 int i=0;
411 for ( ; strv && strv[i]; ++i)
412 if (!g_strcmp0(strv[i], "HubIncoming") && (i>0))
413 set_peer_name(strv[i-1]);
414 g_strfreev(strv);
415 }
416
417 void set_peer_name(const char* peer_name)
418 {
419 g_return_if_fail(peer_name && *peer_name);
420
421 g_debug("changing '%s' peer_name to '%s'", m_ccad_path.c_str(), peer_name);
422 m_peer_name = peer_name;
423
424 /* If we can find a click icon for the peer,
425 Use it as the transfer's icon */
426
427 GError* error = nullptr;
428 auto user = click_user_new_for_user(nullptr, nullptr, &error);
429 if (user != nullptr)
430 {
431 gchar* path = click_user_get_path(user, peer_name, &error);
432 if (path != nullptr)
433 {
434 auto manifest = click_user_get_manifest(user, peer_name, &error);
435 if (manifest != nullptr)
436 {
437 const auto icon_name = json_object_get_string_member(manifest, "icon");
438 if (icon_name != nullptr)
439 {
440 auto filename = g_build_filename(path, icon_name, nullptr);
441 set_icon(filename);
442 g_free(filename);
443 }
444 }
445 g_free(path);
446 }
447 }
448
449 if (error != nullptr)
450 g_warning("Unable to get manifest for '%s' package: %s", peer_name, error->message);
451
452 g_clear_object(&user);
453 g_clear_error(&error);
454 }
455
456 void set_icon(const char* filename)
457 {
458 const std::string tmp = filename ? filename : "";
459 if (app_icon != tmp)
460 {
461 g_debug("changing '%s' icon to '%s'", m_ccad_path.c_str(), tmp.c_str());
462 app_icon = tmp;
463 emit_changed();
464 }
465 }
466
467 /***
468 **** Content Hub
469 ***/
470
471 void get_cucdt_properties()
472 {
473 const auto bus_name = CH_BUS_NAME;
474 const auto object_path = m_cucdt_path.c_str();
475 const auto interface_name = CH_TRANSFER_IFACE_NAME;
476
477 g_dbus_connection_call(m_bus, bus_name, object_path, interface_name,
478 "Store", nullptr, G_VARIANT_TYPE("(s)"),
479 G_DBUS_CALL_FLAGS_NONE, -1,
480 m_cancellable, on_cucdt_store, this);
481 }
482
483 static void on_cucdt_store(GObject* source, GAsyncResult* res, gpointer gself)
484 {
485 auto v = connection_call_finish(source, res, "Unable to get store");
486 if (v != nullptr)
487 {
488 const gchar* store = nullptr;
489 g_variant_get_child(v, 0, "&s", &store);
490 if (store != nullptr)
491 static_cast<DBusTransfer*>(gself)->set_store(store);
492 g_variant_unref(v);
493 }
494 }
495
496 /***
497 **** DownloadManager
498 ***/
499
500 void get_ccad_properties()
501 {
502 const auto bus_name = DM_BUS_NAME;
503 const auto object_path = m_ccad_path.c_str();
504 const auto interface_name = DM_DOWNLOAD_IFACE_NAME;
356505
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,
358 "totalSize", nullptr, G_VARIANT_TYPE("(t)"),507 "totalSize", nullptr, G_VARIANT_TYPE("(t)"),
359 G_DBUS_CALL_FLAGS_NONE, -1,508 G_DBUS_CALL_FLAGS_NONE, -1,
360 m_cancellable, on_total_size, this);509 m_cancellable, on_ccad_total_size, this);
361510
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,
363 "progress", nullptr, G_VARIANT_TYPE("(t)"),512 "progress", nullptr, G_VARIANT_TYPE("(t)"),
364 G_DBUS_CALL_FLAGS_NONE, -1,513 G_DBUS_CALL_FLAGS_NONE, -1,
365 m_cancellable, on_progress, this);514 m_cancellable, on_ccad_progress, this);
366
367 g_dbus_connection_call(m_bus, bus_name, object_path, interface_name,
368 "metadata", nullptr, G_VARIANT_TYPE("(a{sv})"),
369 G_DBUS_CALL_FLAGS_NONE, -1,
370 m_cancellable, on_metadata, this);
371 }515 }
372516
373 static void on_total_size(GObject* connection,517 static void on_ccad_total_size(GObject * source,
374 GAsyncResult* res,518 GAsyncResult * res,
375 gpointer gself)519 gpointer gself)
376 {520 {
377 GError* error = nullptr;521 auto v = connection_call_finish(source, res, "Error calling totalSize()");
378522 if (v != nullptr)
379 auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection),
380 res,
381 &error);
382
383 if (error != nullptr)
384 {
385 if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
386 g_warning("Couldn't get download total size: %s", error->message);
387
388 g_error_free(error);
389 }
390 else
391 {523 {
392 guint64 n = 0;524 guint64 n = 0;
393 g_variant_get_child(v, 0, "t", &n);525 g_variant_get_child(v, 0, "t", &n);
@@ -399,23 +531,12 @@
399 }531 }
400 }532 }
401533
402 static void on_progress(GObject* connection,534 static void on_ccad_progress(GObject * source,
403 GAsyncResult* res,535 GAsyncResult * res,
404 gpointer gself)536 gpointer gself)
405 {537 {
406 GError* error = nullptr;538 auto v = connection_call_finish(source, res, "Error calling progress()");
407 auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection),539 if (v != nullptr)
408 res,
409 &error);
410
411 if (error != nullptr)
412 {
413 if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
414 g_warning("Couldn't get download progress: %s", error->message);
415
416 g_error_free(error);
417 }
418 else
419 {540 {
420 guint64 n = 0;541 guint64 n = 0;
421 g_variant_get_child(v, 0, "t", &n);542 g_variant_get_child(v, 0, "t", &n);
@@ -427,31 +548,63 @@
427 }548 }
428 }549 }
429550
430 static void on_metadata(GObject* connection,551 void call_ccad_method_no_args_no_response(const char* method_name)
431 GAsyncResult* res,552 {
432 gpointer)// gself)553 const auto bus_name = DM_BUS_NAME;
554 const auto object_path = m_ccad_path.c_str();
555 const auto interface_name = DM_DOWNLOAD_IFACE_NAME;
556
557 g_debug("%s transfer %s calling '%s'", G_STRLOC, id.c_str(), method_name);
558
559 g_dbus_connection_call(m_bus, bus_name, object_path, interface_name,
560 method_name, nullptr, nullptr,
561 G_DBUS_CALL_FLAGS_NONE, -1,
562 m_cancellable, nullptr, nullptr);
563 }
564
565 /***
566 ****
567 ***/
568
569 static GVariant* connection_call_finish(GObject * connection,
570 GAsyncResult * res,
571 const char * warning)
433 {572 {
434 GError* error = nullptr;573 GError* error = nullptr;
574
435 auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection),575 auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection),
436 res,576 res,
437 &error);577 &error);
438578
439 if (error != nullptr)579 if (v == nullptr)
440 {580 {
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))
442 g_warning("Couldn't get download metadata: %s", error->message);582 g_warning("%s: %s", warning, error->message);
443583
444 g_error_free(error);584 g_error_free(error);
445 }585 }
446 else586
447 {587 return v;
448 char * variant_str = g_variant_print(v, true);
449 // FIXME: pull appname (and icon) and title from metadata?
450 g_warning("%s unhandled metadata: %s", G_STRFUNC, variant_str);
451 g_free(variant_str);
452 g_variant_unref(v);
453 }
454 }588 }
589
590 core::Signal<> m_changed;
591
592 uint64_t m_received = 0;
593 uint64_t m_total_size = 0;
594 struct DownloadProgress {
595 int64_t time_usec; // microseconds since epoch
596 uint64_t bytes;
597 DownloadProgress operator-(const DownloadProgress& that) {
598 return DownloadProgress{time_usec-that.time_usec, bytes-that.bytes};
599 }
600 };
601 std::vector<DownloadProgress> m_history;
602
603 GDBusConnection* m_bus = nullptr;
604 GCancellable* m_cancellable = nullptr;
605 const std::string m_ccad_path;
606 const std::string m_cucdt_path;
607 std::string m_peer_name;
455};608};
456609
457} // anonymous namespace610} // anonymous namespace
@@ -460,215 +613,305 @@
460****613****
461***/614***/
462615
463DBusWorld::DBusWorld(const std::shared_ptr<MutableModel>& model):616class DBusWorld::Impl
617{
618public:
619
620 Impl(const std::shared_ptr<MutableModel>& model):
464 m_cancellable(g_cancellable_new()),621 m_cancellable(g_cancellable_new()),
465 m_model(model)622 m_model(model)
466{623 {
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);
468}625
469626 m_model->removed().connect([this](const Transfer::Id& id){
470DBusWorld::~DBusWorld()627 auto transfer = find_transfer_by_id(id);
471{628 if (transfer)
629 m_removed_ccad.insert(transfer->ccad_path());
630 });
631 }
632
633 ~Impl()
634 {
472 g_cancellable_cancel(m_cancellable);635 g_cancellable_cancel(m_cancellable);
473 g_clear_object(&m_cancellable);636 g_clear_object(&m_cancellable);
474 set_bus(nullptr);637 set_bus(nullptr);
475 g_clear_object(&m_bus);638 g_clear_object(&m_bus);
476}639 }
477640
478void DBusWorld::on_bus_ready(GObject* /*source_object*/,641 void start(const Transfer::Id& id)
479 GAsyncResult* res,642 {
480 gpointer gself)643 auto transfer = find_transfer_by_id(id);
481{644 g_return_if_fail(transfer);
482 GError* error = nullptr;645 transfer->start();
483 auto bus = g_bus_get_finish(res, &error);646 }
484647
485 if (error != nullptr)648 void pause(const Transfer::Id& id)
486 {649 {
487 if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))650 auto transfer = find_transfer_by_id(id);
488 g_warning("Could not get session bus: %s", error->message);651 g_return_if_fail(transfer);
489652 transfer->pause();
490 g_error_free(error);653 }
491 }654
492 else655 void resume(const Transfer::Id& id)
493 {656 {
494 static_cast<DBusWorld*>(gself)->set_bus(bus);657 auto transfer = find_transfer_by_id(id);
495 g_object_unref(bus);658 g_return_if_fail(transfer);
496 }659 transfer->resume();
497}660 }
498661
499void DBusWorld::set_bus(GDBusConnection* bus)662 void cancel(const Transfer::Id& id)
500{663 {
501 if (m_bus != nullptr)664 auto transfer = find_transfer_by_id(id);
502 {665 g_return_if_fail(transfer);
503 for (const auto& tag : m_signal_subscriptions)666 transfer->cancel();
504 g_dbus_connection_signal_unsubscribe(m_bus, tag);667 }
505668
506 m_signal_subscriptions.clear();669 void open(const Transfer::Id& id)
507 g_clear_object(&m_bus);670 {
508 }671 auto transfer = find_transfer_by_id(id);
509672 g_return_if_fail(transfer);
510 if (bus != nullptr)673 transfer->open();
511 {674 transfer->open_app();
512 g_debug("%s: %s", G_STRFUNC, g_dbus_connection_get_unique_name(bus));675 }
513 m_bus = G_DBUS_CONNECTION(g_object_ref(bus));676
514677 void open_app(const Transfer::Id& id)
515 guint tag;678 {
516 tag = g_dbus_connection_signal_subscribe(bus,679 auto transfer = find_transfer_by_id(id);
517 BUS_NAME,680 g_return_if_fail(transfer);
518 DOWNLOAD_MANAGER_IFACE_NAME,681 transfer->open_app();
519 "downloadCreated",682 }
520 "/",683
521 nullptr,684private:
522 G_DBUS_SIGNAL_FLAGS_NONE,685
523 on_download_created,686 static void on_bus_ready(GObject * /*source_object*/,
524 this,687 GAsyncResult * res,
525 nullptr);688 gpointer gself)
526 m_signal_subscriptions.insert(tag);689 {
527690 GError* error = nullptr;
528 tag = g_dbus_connection_signal_subscribe (bus,691 auto bus = g_bus_get_finish(res, &error);
529 BUS_NAME,692
530 DOWNLOAD_IFACE_NAME,693 if (bus == nullptr)
531 nullptr,694 {
532 nullptr,695 if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
533 nullptr,696 g_warning("Could not get session bus: %s", error->message);
534 G_DBUS_SIGNAL_FLAGS_NONE,697
535 on_download_signal,698 g_error_free(error);
536 this,699 }
537 nullptr);700 else
538 m_signal_subscriptions.insert(tag);701 {
539 }702 static_cast<Impl*>(gself)->set_bus(bus);
540}703 g_object_unref(bus);
541704 }
542namespace705 }
543{706
544 std::shared_ptr<DBusTransfer>707 void set_bus(GDBusConnection* bus)
545 find_dbus_transfer_for_object_path(const std::shared_ptr<Model>& model,708 {
546 const std::string& object_path)709 if (m_bus != nullptr)
547 {710 {
548 std::shared_ptr<DBusTransfer> dbus_transfer;711 for (const auto& tag : m_signal_subscriptions)
549712 g_dbus_connection_signal_unsubscribe(m_bus, tag);
550 for (const auto& transfer : model->get_all())713
551 {714 m_signal_subscriptions.clear();
552 const auto tmp = std::static_pointer_cast<DBusTransfer>(transfer);715 g_clear_object(&m_bus);
553716 }
554 if (tmp && (tmp->object_path()==object_path))717
555 {718 if (bus != nullptr)
556 dbus_transfer = tmp;719 {
557 break;720 g_debug("%s: %s", G_STRFUNC, g_dbus_connection_get_unique_name(bus));
558 }721 m_bus = G_DBUS_CONNECTION(g_object_ref(bus));
559 }722
560723 guint tag;
561 return dbus_transfer;724
562 }725 tag = g_dbus_connection_signal_subscribe(bus,
563}726 DM_BUS_NAME,
564727 DM_DOWNLOAD_IFACE_NAME,
565void DBusWorld::on_download_signal(GDBusConnection*, //connection,728 nullptr,
566 const gchar*, //sender_name,729 nullptr,
567 const gchar* object_path,730 nullptr,
568 const gchar*, //interface_name,731 G_DBUS_SIGNAL_FLAGS_NONE,
569 const gchar* signal_name,732 on_download_signal,
570 GVariant* parameters,733 this,
571 gpointer gself)734 nullptr);
572{735 m_signal_subscriptions.insert(tag);
573 auto self = static_cast<DBusWorld*>(gself);736
574737 tag = g_dbus_connection_signal_subscribe(bus,
575 auto dbus_transfer = find_dbus_transfer_for_object_path(self->m_model, object_path);738 CH_BUS_NAME,
576739 CH_TRANSFER_IFACE_NAME,
577 if (!dbus_transfer)740 nullptr,
578 {741 nullptr,
579 g_message("A %s that we didn't know about just emitted signal '%s' -- "742 nullptr,
580 "might be a transfer that was here before us?",743 G_DBUS_SIGNAL_FLAGS_NONE,
581 DOWNLOAD_IFACE_NAME, signal_name);744 on_transfer_signal_static,
582 self->add_transfer(object_path);745 this,
583 dbus_transfer = find_dbus_transfer_for_object_path(self->m_model, object_path);746 nullptr);
584 g_return_if_fail (dbus_transfer);747 m_signal_subscriptions.insert(tag);
585 }748 }
586749 }
587 dbus_transfer->handle_signal(signal_name, parameters);750
588}751 static void on_transfer_signal_static(GDBusConnection* /*connection*/,
589752 const gchar* /*sender_name*/,
590void DBusWorld::on_download_created(GDBusConnection*, //connection,753 const gchar* object_path,
591 const gchar*, //sender_name,754 const gchar* /*interface_name*/,
592 const gchar*, //object_path,755 const gchar* signal_name,
593 const gchar*, //interface_name,756 GVariant* parameters,
594 const gchar*, //signal_name,757 gpointer gself)
595 GVariant* parameters,758 {
596 gpointer gself)759 static_cast<Impl*>(gself)->on_transfer_signal(object_path, signal_name, parameters);
597{760 }
598 gchar* download_path = nullptr;761
599 g_variant_get_child(parameters, 0, "o", &download_path);762 void on_transfer_signal(const gchar* cucdt_path,
600763 const gchar* signal_name,
601 if (download_path != nullptr)764 GVariant* parameters)
602 {765 {
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));
604 static_cast<DBusWorld*>(gself)->add_transfer(download_path);767
605768 if (!g_strcmp0(signal_name, "DownloadIdChanged"))
606 g_free(download_path);769 {
607 }770 const char* ccad_path = nullptr;
608}771 g_variant_get_child(parameters, 0, "&s", &ccad_path);
609772 g_return_if_fail(cucdt_path != nullptr);
610void DBusWorld::add_transfer(const char* object_path)773
611{774 // ensure this ccad/cucdt pair is tracked
612 // create a new Transfer and pass it to the model775 if (!find_transfer_by_ccad_path(ccad_path))
613 auto dbus_transfer = new DBusTransfer(m_bus, object_path);776 create_new_transfer(ccad_path, cucdt_path);
614 std::shared_ptr<Transfer> transfer(dbus_transfer);777 }
615 m_model->add(transfer);778 else
616779 {
617 // notify the model whenever the Transfer changes780 // Route this signal to the DBusTransfer for processing
618 const auto id = dbus_transfer->id;781 auto transfer = find_transfer_by_cucdt_path(cucdt_path);
619 dbus_transfer->changed().connect([this,id]{782 if (transfer)
620 if (m_model->get(id))783 transfer->handle_cucdt_signal(signal_name, parameters);
621 m_model->emit_changed(id);784 }
622 });785 }
623}786
624787 static void on_download_signal(GDBusConnection* /*connection*/,
625namespace788 const gchar* /*sender_name*/,
626{789 const gchar* ccad_path,
627 std::shared_ptr<DBusTransfer>790 const gchar* /*interface_name*/,
628 get_dbus_transfer(const std::shared_ptr<Model>& model, const Transfer::Id& id)791 const gchar* signal_name,
629 {792 GVariant* parameters,
630 auto transfer = model->get(id);793 gpointer gself)
794 {
795 g_debug("download signal: %s %s %s", ccad_path, signal_name, g_variant_print(parameters, TRUE));
796
797 // Route this signal to the DBusTransfer for processing
798 auto self = static_cast<Impl*>(gself);
799 auto transfer = self->find_transfer_by_ccad_path(ccad_path);
800 if (transfer)
801 transfer->handle_ccad_signal(signal_name, parameters);
802 }
803
804 /***
805 ****
806 ***/
807
808 std::shared_ptr<DBusTransfer> find_transfer_by_ccad_path(const std::string& path)
809 {
810 for (const auto& transfer : m_model->get_all())
811 {
812 const auto tmp = std::static_pointer_cast<DBusTransfer>(transfer);
813
814 if (tmp && (path == tmp->ccad_path()))
815 return tmp;
816 }
817
818 return nullptr;
819 }
820
821 std::shared_ptr<DBusTransfer> find_transfer_by_cucdt_path(const std::string& path)
822 {
823 for (const auto& transfer : m_model->get_all())
824 {
825 const auto tmp = std::static_pointer_cast<DBusTransfer>(transfer);
826
827 if (tmp && (path == tmp->cucdt_path()))
828 return tmp;
829 }
830
831 return nullptr;
832 }
833
834 void create_new_transfer(const std::string& ccad_path,
835 const std::string& cucdt_path)
836 {
837 // don't let transfers reappear after they've been cleared by the user
838 if (m_removed_ccad.count(ccad_path))
839 return;
840
841 auto new_transfer = std::make_shared<DBusTransfer>(m_bus, ccad_path, cucdt_path);
842
843 m_model->add(new_transfer);
844
845 // when one of the DBusTransfer's properties changes,
846 // emit a change signal for the model
847 const auto id = new_transfer->id;
848 new_transfer->changed().connect([this,id]{
849 if (m_model->get(id))
850 m_model->emit_changed(id);
851 });
852 }
853
854 std::shared_ptr<DBusTransfer> find_transfer_by_id(const Transfer::Id& id)
855 {
856 auto transfer = m_model->get(id);
631 g_return_val_if_fail(transfer, std::shared_ptr<DBusTransfer>());857 g_return_val_if_fail(transfer, std::shared_ptr<DBusTransfer>());
632 return std::static_pointer_cast<DBusTransfer>(transfer);858 return std::static_pointer_cast<DBusTransfer>(transfer);
633 }859 }
634}860
635861 GDBusConnection* m_bus = nullptr;
636void DBusWorld::start(const Transfer::Id& id)862 GCancellable* m_cancellable = nullptr;
637{863 std::set<guint> m_signal_subscriptions;
638 auto dbus_transfer = get_dbus_transfer (m_model, id);864 std::shared_ptr<MutableModel> m_model;
639 g_return_if_fail(dbus_transfer);865 std::set<std::string> m_removed_ccad;
640 dbus_transfer->start();866};
641}867
642868/***
643void DBusWorld::pause(const Transfer::Id& id)869****
644{870***/
645 auto dbus_transfer = get_dbus_transfer (m_model, id);871
646 g_return_if_fail(dbus_transfer);872DBusWorld::DBusWorld(const std::shared_ptr<MutableModel>& model):
647 dbus_transfer->pause();873 impl(new Impl(model))
648}874{
649875}
650void DBusWorld::resume(const Transfer::Id& id)876
651{877DBusWorld::~DBusWorld()
652 auto dbus_transfer = get_dbus_transfer (m_model, id);878{
653 g_return_if_fail(dbus_transfer);879}
654 dbus_transfer->resume();880
655}881void
656882DBusWorld::open(const Transfer::Id& id)
657void DBusWorld::cancel(const Transfer::Id& id)883{
658{884 impl->open(id);
659 auto dbus_transfer = get_dbus_transfer (m_model, id);885}
660 g_return_if_fail(dbus_transfer);886
661 dbus_transfer->cancel();887void
662}888DBusWorld::start(const Transfer::Id& id)
663889{
664void DBusWorld::open(const Transfer::Id& id)890 impl->start(id);
665{891}
666 std::cerr << G_STRFUNC << " FIXME " << id << std::endl;892
667}893void
668894DBusWorld::pause(const Transfer::Id& id)
669void DBusWorld::open_app(const Transfer::Id& id)895{
670{896 impl->pause(id);
671 std::cerr << G_STRFUNC << " FIXME " << id << std::endl;897}
898
899void
900DBusWorld::resume(const Transfer::Id& id)
901{
902 impl->resume(id);
903}
904
905void
906DBusWorld::cancel(const Transfer::Id& id)
907{
908 impl->cancel(id);
909}
910
911void
912DBusWorld::open_app(const Transfer::Id& id)
913{
914 impl->open_app(id);
672}915}
673916
674/***917/***
675918
=== modified file 'tests/manual'
--- tests/manual 2014-06-18 15:26:37 +0000
+++ tests/manual 2014-08-26 20:52:17 +0000
@@ -1,23 +1,23 @@
11
2Test-case indicator-transfer/simple-download-check2Test-case indicator-transfer/download-an-image
3<dl>3<dl>
4 <dt>Ensure indicator-transfer-service is running<dt>4 <dt>Ensure indicator-transfer-service is running<dt>
5 <dd>The indicator should be visible in the panel</dd>5 <dd>The indicator should be visible in the panel</dd>
66 <dt>Start downloading an image from the Browser to the Gallery.
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'
8 click or pull down on the indicator so that its menu is visible8 in the popup, and select 'Gallery' at the 'Open with ' prompt.</dt>
9 and its menuitems can be observed while the downloads run.</dt>9 <dd>In the 'Ongoing Transfers' section, the transfer should appear</dd>
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>
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>
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>
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>
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>
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>
16 <dd>As the last transfer finishes, the 'Pause all' button should disappear</dd>16 <dt>Click on the completed transfer's menuitem</dt>
1717 <dd>The gallery app should launch</dd>
18 <dt>After all the transfers finish, press the 'Clear all' button.</dt>18 <dt>Press the 'Clear all' button.</dt>
19 <dd>All three transfers should be cleared from the menu.</dd>19 <dd>The transfer should be cleared from the menu.</dd>
20 <dd>The 'Clear all' button should disappear</dd>20 <dd>The 'Clear all' button should disappear</dd>
21</dt>21</dt>
2222
23<strong>23<strong>

Subscribers

People subscribed via source and target branches