16
16
#include < libcyphal/types.hpp>
17
17
18
18
#include < uavcan/file/Error_1_0.hpp>
19
+ #include < uavcan/file/GetInfo_0_2.hpp>
19
20
#include < uavcan/file/Read_1_1.hpp>
21
+ #include < uavcan/primitive/Unstructured_1_0.hpp>
20
22
23
+ #include < chrono>
24
+ #include < cstddef>
25
+ #include < iostream>
21
26
#include < utility>
22
27
23
28
class FileDownloader final
@@ -34,9 +39,12 @@ class FileDownloader final
34
39
FileDownloader (FileDownloader&& other) noexcept
35
40
: presentation_{other.presentation_ }
36
41
, time_provider_{other.time_provider_ }
42
+ , get_info_client_{std::move (other.get_info_client_ )}
43
+ , get_info_promise_{std::move (other.get_info_promise_ )}
37
44
, read_client_{std::move (other.read_client_ )}
38
- , response_promise_{std::move (other.response_promise_ )}
39
- , current_request_{std::move (other.current_request_ )}
45
+ , read_promise_{std::move (other.read_promise_ )}
46
+ , read_request_{std::move (other.read_request_ )}
47
+ , file_stats_{other.file_stats_ }
40
48
{
41
49
}
42
50
@@ -48,88 +56,243 @@ class FileDownloader final
48
56
49
57
bool start (const libcyphal::transport::NodeId remote_node_id, const cetl::string_view file_path)
50
58
{
51
- response_promise_.reset ();
59
+ get_info_promise_.reset ();
60
+ get_info_client_.reset ();
61
+ read_promise_.reset ();
52
62
read_client_.reset ();
53
63
54
- auto maybe_client = presentation_.makeClient <ReadService>(remote_node_id);
55
- if (const auto * const failure = cetl::get_if<Presentation::MakeFailure>(&maybe_client))
64
+ file_stats_.file_size = 0 ;
65
+ file_stats_.file_progress = 0 ;
66
+ file_stats_.file_error .value = uavcan::file::Error_1_0::OK;
67
+
68
+ get_info_client_ = makeClient<Svc::GetInfo>(" GetInfo" , remote_node_id);
69
+ read_client_ = makeClient<Svc::Read>(" Read" , remote_node_id);
70
+ if (!get_info_client_ || !read_client_)
56
71
{
57
- ( void ) failure ;
72
+ file_stats_. file_error . value = uavcan::file::Error_1_0::UNKNOWN_ERROR ;
58
73
return false ;
59
74
}
60
- read_client_.emplace (cetl::get<ReadClient>(std::move (maybe_client)));
61
75
62
- current_request_ .offset = 0 ;
63
- current_request_ .path .path = {file_path.begin (), file_path.end (), &presentation_.memory ()};
76
+ read_request_ .offset = 0 ;
77
+ read_request_ .path .path = {file_path.begin (), file_path.end (), &presentation_.memory ()};
64
78
65
- return initiateNextRequest ();
79
+ std::cout << " Getting file info (path='" << file_path << " ')...\n " ;
80
+ Svc::GetInfo::Request gi_request{&presentation_.memory ()};
81
+ gi_request.path .path = {file_path.begin (), file_path.end (), &presentation_.memory ()};
82
+ return makeRequest<Svc::GetInfo>(" GetInfo" ,
83
+ get_info_client_,
84
+ get_info_promise_,
85
+ gi_request,
86
+ [this ](const auto & arg) {
87
+ //
88
+ handleGetInfoPromiseResult (arg.result );
89
+ });
66
90
}
67
91
68
92
private:
69
- using Presentation = libcyphal::presentation::Presentation;
93
+ using NodeId = libcyphal::transport::NodeId;
94
+ using Presentation = libcyphal::presentation::Presentation;
95
+ using UnstructuredData = uavcan::primitive::Unstructured_1_0;
96
+ using ResponsePromiseFailure = libcyphal::presentation::ResponsePromiseFailure;
97
+
98
+ template <typename T>
99
+ struct SvcSpec : T
100
+ {
101
+ using Client = libcyphal::presentation::ServiceClient<T>;
102
+ using Promise = libcyphal::presentation::ResponsePromise<typename T::Response>;
103
+ using Failure = typename Client::Failure;
104
+ };
105
+ struct Svc
106
+ {
107
+ using Read = SvcSpec<uavcan::file::Read_1_1>;
108
+ using GetInfo = SvcSpec<uavcan::file::GetInfo_0_2>;
109
+
110
+ }; // Svc
111
+
112
+ struct FileStats
113
+ {
114
+ uavcan::file::Error_1_0 file_error;
115
+ libcyphal::TimePoint start_time;
116
+ std::size_t file_size{0 };
117
+ std::size_t file_progress{0 };
70
118
71
- using ReadService = uavcan::file::Read_1_1;
72
- using ReadClient = libcyphal::presentation::ServiceClient<ReadService>;
73
- using ResponsePromise = libcyphal::presentation::ResponsePromise<ReadService::Response>;
74
- using ResponseData = ReadService::Response::_traits_::TypeOf::data;
119
+ }; // FileStats
75
120
76
121
FileDownloader (Presentation& presentation, libcyphal::ITimeProvider& time_provider)
77
122
: presentation_{presentation}
78
123
, time_provider_{time_provider}
79
124
{
80
125
}
81
126
82
- bool initiateNextRequest ()
127
+ template <typename Service>
128
+ auto makeClient (const cetl::string_view role, const NodeId server_node_id)
129
+ -> cetl::optional<typename Service::Client>
130
+ {
131
+ auto maybe_client = presentation_.makeClient <Service>(server_node_id);
132
+ if (const auto * const failure = cetl::get_if<Presentation::MakeFailure>(&maybe_client))
133
+ {
134
+ (void ) failure;
135
+ std::cerr << " Can't make '" << role << " ' client.\n " ;
136
+ return cetl::nullopt;
137
+ }
138
+ return cetl::get<typename Service::Client>(std::move (maybe_client));
139
+ }
140
+
141
+ template <typename Service, typename Handler>
142
+ bool makeRequest ( //
143
+ const cetl::string_view role,
144
+ cetl::optional<typename Service::Client>& client,
145
+ cetl::optional<typename Service::Promise>& promise,
146
+ const typename Service::Request& request,
147
+ Handler&& handler,
148
+ const libcyphal::Duration timeout = std::chrono::seconds{1 })
83
149
{
84
- response_promise_ .reset ();
150
+ promise .reset ();
85
151
86
- constexpr auto timeout = std::chrono::seconds{1 };
87
- auto maybe_promise = read_client_->request (time_provider_.now () + timeout, current_request_);
88
- if (const auto * const failure = cetl::get_if<ReadClient::Failure>(&maybe_promise))
152
+ auto maybe_promise = client->request (time_provider_.now () + timeout, request);
153
+ if (const auto * const failure = cetl::get_if<typename Service::Failure>(&maybe_promise))
89
154
{
90
155
(void ) failure;
91
- read_client_.reset ();
156
+ file_stats_.file_error .value = uavcan::file::Error_1_0::UNKNOWN_ERROR;
157
+ std::cerr << " Can't make '" << role << " ' request.\n " ;
158
+ complete ();
92
159
return false ;
93
160
}
94
- response_promise_.emplace (cetl::get<ResponsePromise>(std::move (maybe_promise)));
95
- response_promise_->setCallback ([this ](const auto & args) {
161
+
162
+ promise.emplace (cetl::get<typename Service::Promise>(std::move (maybe_promise)));
163
+ promise->setCallback (std::forward<Handler>(handler));
164
+ return true ;
165
+ }
166
+
167
+ void handleGetInfoPromiseResult (const Svc::GetInfo::Promise::Result& result)
168
+ {
169
+ if (const auto * const failure = cetl::get_if<ResponsePromiseFailure>(&result))
170
+ {
171
+ (void ) failure;
172
+ file_stats_.file_error .value = uavcan::file::Error_1_0::UNKNOWN_ERROR;
173
+ std::cerr << " GetInfo request failed.\n " ;
174
+ complete ();
175
+ return ;
176
+ }
177
+ const auto success = cetl::get<Svc::GetInfo::Promise::Success>(result);
178
+
179
+ get_info_promise_.reset ();
180
+ get_info_client_.reset ();
181
+
182
+ const auto & response = success.response ;
183
+ if (response._error .value == uavcan::file::Error_1_0::OK)
184
+ {
185
+ file_stats_.file_size = response.size ;
186
+ std::cout << " Downloading (size=" << file_stats_.file_size << " ) ...\n " ;
187
+ if (file_stats_.file_size > 0 )
188
+ {
189
+ file_stats_.start_time = time_provider_.now ();
190
+
191
+ printProgress ();
192
+ initiateNextReadRequest ();
193
+ return ;
194
+ }
195
+
196
+ file_stats_.file_error .value = uavcan::file::Error_1_0::OK;
197
+ }
198
+ else
199
+ {
200
+ file_stats_.file_error = response._error ;
201
+ std::cerr << " Can't get file info (err=" << response._error .value << " ).\n " ;
202
+ }
203
+
204
+ complete ();
205
+ }
206
+
207
+ bool initiateNextReadRequest ()
208
+ {
209
+ return makeRequest<Svc::Read>(" Read" , read_client_, read_promise_, read_request_, [this ](const auto & arg) {
96
210
//
97
- handlePromiseResult ( std::move (args .result ) );
211
+ handleReadPromiseResult (arg .result );
98
212
});
213
+ }
99
214
100
- return true ;
215
+ void handleReadPromiseResult (Svc::Read::Promise::Result result)
216
+ {
217
+ if (const auto * const failure = cetl::get_if<ResponsePromiseFailure>(&result))
218
+ {
219
+ (void ) failure;
220
+ file_stats_.file_error .value = uavcan::file::Error_1_0::UNKNOWN_ERROR;
221
+ std::cerr << " Read request failed.\n " ;
222
+ complete ();
223
+ return ;
224
+ }
225
+ const auto success = cetl::get<Svc::Read::Promise::Success>(std::move (result));
226
+
227
+ const auto & response = success.response ;
228
+ if (response._error .value == uavcan::file::Error_1_0::OK)
229
+ {
230
+ const auto data_size = response.data .value .size ();
231
+ read_request_.offset += response.data .value .size ();
232
+
233
+ printProgress ();
234
+
235
+ // Are we done?
236
+ if (data_size == UnstructuredData::_traits_::ArrayCapacity::value)
237
+ {
238
+ initiateNextReadRequest ();
239
+ return ;
240
+ }
241
+ }
242
+ else
243
+ {
244
+ file_stats_.file_error = response._error ;
245
+ std::cerr << " Can't read file (err=" << response._error .value << " ).\n " ;
246
+ }
247
+ complete ();
101
248
}
102
249
103
- void handlePromiseResult (ResponsePromise::Result result )
250
+ void printProgress ( )
104
251
{
105
- if (const auto * const success = cetl::get_if<ResponsePromise::Success>(&result))
252
+ CETL_DEBUG_ASSERT (file_stats_.file_size > 0 , " " );
253
+ CETL_DEBUG_ASSERT (read_request_.offset <= file_stats_.file_size , " " );
254
+
255
+ const auto progress = (read_request_.offset * 100U ) / file_stats_.file_size ;
256
+ CETL_DEBUG_ASSERT (progress <= 100U , " " );
257
+
258
+ // Print progress only if its integer % has changed (or in the beginning).
259
+ if ((progress != file_stats_.file_progress ) || (read_request_.offset == 0 ))
106
260
{
107
- const auto & response = success->response ;
108
- if (response._error .value == uavcan::file::Error_1_0::OK)
261
+ file_stats_.file_progress = progress;
262
+ const auto duration = time_provider_.now () - file_stats_.start_time ;
263
+ if (const auto duration_us = std::chrono::duration_cast<std::chrono::microseconds>(duration).count ())
264
+ {
265
+ const auto speed_kb_per_sec = (read_request_.offset * 1000000U ) / (duration_us * 1024U );
266
+ std::cout << " \r progress " << progress << " % (speed=" << speed_kb_per_sec << " KB/s) "
267
+ << std::flush;
268
+ }
269
+ else
109
270
{
110
- const auto data_size = response.data .value .size ();
111
- current_request_.offset += response.data .value .size ();
112
-
113
- // Are we done?
114
- if (data_size == ResponseData::_traits_::ArrayCapacity::value)
115
- {
116
- initiateNextRequest ();
117
- return ;
118
- }
271
+ std::cout << " \r progress " << progress << " %" << std::flush;
119
272
}
120
273
}
121
- std::cout << " Download complete\n " ;
122
- response_promise_.reset ();
274
+ }
275
+
276
+ void complete ()
277
+ {
278
+ std::cout << " \n Download completed (err=" << file_stats_.file_error .value << " ).\n " << std::flush;
279
+
280
+ get_info_promise_.reset ();
281
+ get_info_client_.reset ();
282
+ read_promise_.reset ();
123
283
read_client_.reset ();
124
284
}
125
285
126
286
// MARK: Data members:
127
287
128
- Presentation& presentation_;
129
- libcyphal::ITimeProvider& time_provider_;
130
- cetl::optional<ReadClient> read_client_;
131
- cetl::optional<ResponsePromise> response_promise_;
132
- ReadService::Request current_request_{&presentation_.memory ()};
288
+ Presentation& presentation_;
289
+ libcyphal::ITimeProvider& time_provider_;
290
+ cetl::optional<Svc::GetInfo::Client> get_info_client_;
291
+ cetl::optional<Svc::GetInfo::Promise> get_info_promise_;
292
+ cetl::optional<Svc::Read::Client> read_client_;
293
+ cetl::optional<Svc::Read::Promise> read_promise_;
294
+ Svc::Read::Request read_request_{&presentation_.memory ()};
295
+ FileStats file_stats_;
133
296
134
297
}; // FileDownloader
135
298
0 commit comments