• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2023 Huawei Device Co., Ltd.
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 //     http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 use std::io::{self, SeekFrom};
15 use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, AtomicU64, Ordering};
16 use std::sync::{Arc, Mutex};
17 use std::time::Duration;
18 
19 use ylong_http_client::async_impl::{Body, Client, Request, RequestBuilder, Response};
20 use ylong_http_client::{ErrorKind, HttpClientError};
21 use ylong_runtime::io::{AsyncSeekExt, AsyncWriteExt};
22 
23 cfg_oh! {
24     use crate::manage::SystemConfig;
25 }
26 
27 use super::config::{Mode, Version};
28 use super::info::{CommonTaskInfo, State, TaskInfo, UpdateInfo};
29 use super::notify::{EachFileStatus, NotifyData, Progress};
30 use super::reason::Reason;
31 use crate::error::ErrorCode;
32 use crate::manage::database::RequestDb;
33 use crate::manage::network_manager::NetworkManager;
34 use crate::manage::notifier::Notifier;
35 use crate::service::client::ClientManagerEntry;
36 use crate::service::notification_bar::NotificationDispatcher;
37 use crate::task::client::build_client;
38 use crate::task::config::{Action, TaskConfig};
39 use crate::task::files::{AttachedFiles, Files};
40 use crate::utils::form_item::FileSpec;
41 use crate::utils::get_current_timestamp;
42 
43 const RETRY_TIMES: u32 = 4;
44 const RETRY_INTERVAL: u64 = 400;
45 
46 pub(crate) struct RequestTask {
47     pub(crate) conf: TaskConfig,
48     pub(crate) client: Client,
49     pub(crate) files: Files,
50     pub(crate) body_files: Files,
51     pub(crate) ctime: u64,
52     pub(crate) mime_type: Mutex<String>,
53     pub(crate) progress: Mutex<Progress>,
54     pub(crate) status: Mutex<TaskStatus>,
55     pub(crate) code: Mutex<Vec<Reason>>,
56     pub(crate) tries: AtomicU32,
57     pub(crate) background_notify_time: AtomicU64,
58     pub(crate) background_notify: Arc<AtomicBool>,
59     pub(crate) file_total_size: AtomicI64,
60     pub(crate) rate_limiting: AtomicU64,
61     pub(crate) last_notify: AtomicU64,
62     pub(crate) client_manager: ClientManagerEntry,
63     pub(crate) running_result: Mutex<Option<Result<(), Reason>>>,
64     pub(crate) timeout_tries: AtomicU32,
65     pub(crate) upload_resume: AtomicBool,
66 }
67 
68 impl RequestTask {
task_id(&self) -> u3269     pub(crate) fn task_id(&self) -> u32 {
70         self.conf.common_data.task_id
71     }
72 
uid(&self) -> u6473     pub(crate) fn uid(&self) -> u64 {
74         self.conf.common_data.uid
75     }
76 
config(&self) -> &TaskConfig77     pub(crate) fn config(&self) -> &TaskConfig {
78         &self.conf
79     }
80 
81     // only use for download task
mime_type(&self) -> String82     pub(crate) fn mime_type(&self) -> String {
83         self.mime_type.lock().unwrap().clone()
84     }
85 
action(&self) -> Action86     pub(crate) fn action(&self) -> Action {
87         self.conf.common_data.action
88     }
89 
mode(&self) -> Mode90     pub(crate) fn mode(&self) -> Mode {
91         self.conf.common_data.mode
92     }
93 
bundle(&self) -> &str94     pub(crate) fn bundle(&self) -> &str {
95         self.conf.bundle.as_str()
96     }
97 
speed_limit(&self, limit: u64)98     pub(crate) fn speed_limit(&self, limit: u64) {
99         let old = self.rate_limiting.swap(limit, Ordering::SeqCst);
100         if old != limit {
101             info!("task {} speed_limit {}", self.task_id(), limit);
102         }
103     }
104 
network_retry(&self) -> Result<(), TaskError>105     pub(crate) async fn network_retry(&self) -> Result<(), TaskError> {
106         if self.tries.load(Ordering::SeqCst) < RETRY_TIMES {
107             self.tries.fetch_add(1, Ordering::SeqCst);
108             if !NetworkManager::is_online() {
109                 return Err(TaskError::Waiting(TaskPhase::NetworkOffline));
110             } else {
111                 ylong_runtime::time::sleep(Duration::from_millis(RETRY_INTERVAL)).await;
112                 return Err(TaskError::Waiting(TaskPhase::NeedRetry));
113             }
114         }
115         Ok(())
116     }
117 }
118 
change_upload_size(begins: u64, mut ends: i64, size: i64) -> i64119 pub(crate) fn change_upload_size(begins: u64, mut ends: i64, size: i64) -> i64 {
120     if ends < 0 || ends >= size {
121         ends = size - 1;
122     }
123     if begins as i64 > ends {
124         return size;
125     }
126     ends - begins as i64 + 1
127 }
128 
129 impl RequestTask {
new( config: TaskConfig, files: AttachedFiles, client: Client, client_manager: ClientManagerEntry, upload_resume: bool, ) -> RequestTask130     pub(crate) fn new(
131         config: TaskConfig,
132         files: AttachedFiles,
133         client: Client,
134         client_manager: ClientManagerEntry,
135         upload_resume: bool,
136     ) -> RequestTask {
137         let file_len = files.files.len();
138         let action = config.common_data.action;
139 
140         let file_total_size = match action {
141             Action::Upload => {
142                 let mut file_total_size = 0i64;
143                 // If the total size overflows, ignore it.
144                 for size in files.sizes.iter() {
145                     file_total_size += *size;
146                 }
147                 file_total_size
148             }
149             Action::Download => -1,
150             _ => unreachable!("Action::Any in RequestTask::new never reach"),
151         };
152 
153         let mut sizes = files.sizes.clone();
154 
155         if action == Action::Upload && config.common_data.index < sizes.len() as u32 {
156             sizes[config.common_data.index as usize] = change_upload_size(
157                 config.common_data.begins,
158                 config.common_data.ends,
159                 sizes[config.common_data.index as usize],
160             );
161         }
162 
163         let time = get_current_timestamp();
164         let status = TaskStatus::new(time);
165         let progress = Progress::new(sizes);
166 
167         RequestTask {
168             conf: config,
169             client,
170             files: files.files,
171             body_files: files.body_files,
172             ctime: time,
173             mime_type: Mutex::new(String::new()),
174             progress: Mutex::new(progress),
175             tries: AtomicU32::new(0),
176             status: Mutex::new(status),
177             code: Mutex::new(vec![Reason::Default; file_len]),
178             background_notify_time: AtomicU64::new(time),
179             background_notify: Arc::new(AtomicBool::new(false)),
180             file_total_size: AtomicI64::new(file_total_size),
181             rate_limiting: AtomicU64::new(0),
182             last_notify: AtomicU64::new(time),
183             client_manager,
184             running_result: Mutex::new(None),
185             timeout_tries: AtomicU32::new(0),
186             upload_resume: AtomicBool::new(upload_resume),
187         }
188     }
189 
new_by_info( config: TaskConfig, #[cfg(feature = "oh")] system: SystemConfig, info: TaskInfo, client_manager: ClientManagerEntry, upload_resume: bool, ) -> Result<RequestTask, ErrorCode>190     pub(crate) fn new_by_info(
191         config: TaskConfig,
192         #[cfg(feature = "oh")] system: SystemConfig,
193         info: TaskInfo,
194         client_manager: ClientManagerEntry,
195         upload_resume: bool,
196     ) -> Result<RequestTask, ErrorCode> {
197         #[cfg(feature = "oh")]
198         let (files, client) = check_config(&config, system)?;
199         #[cfg(not(feature = "oh"))]
200         let (files, client) = check_config(&config)?;
201 
202         let file_len = files.files.len();
203         let action = config.common_data.action;
204         let time = get_current_timestamp();
205 
206         let file_total_size = match action {
207             Action::Upload => {
208                 let mut file_total_size = 0i64;
209                 // If the total size overflows, ignore it.
210                 for size in files.sizes.iter() {
211                     file_total_size += *size;
212                 }
213                 file_total_size
214             }
215             Action::Download => *info.progress.sizes.first().unwrap_or(&-1),
216             _ => unreachable!("Action::Any in RequestTask::new never reach"),
217         };
218 
219         // If `TaskInfo` is provided, use data of it.
220         let ctime = info.common_data.ctime;
221         let mime_type = info.mime_type.clone();
222         let tries = info.common_data.tries;
223         let status = TaskStatus {
224             mtime: time,
225             state: State::from(info.progress.common_data.state),
226             reason: Reason::from(info.common_data.reason),
227         };
228         let progress = info.progress;
229 
230         let mut task = RequestTask {
231             conf: config,
232             client,
233             files: files.files,
234             body_files: files.body_files,
235             ctime,
236             mime_type: Mutex::new(mime_type),
237             progress: Mutex::new(progress),
238             tries: AtomicU32::new(tries),
239             status: Mutex::new(status),
240             code: Mutex::new(vec![Reason::Default; file_len]),
241             background_notify_time: AtomicU64::new(time),
242             background_notify: Arc::new(AtomicBool::new(false)),
243             file_total_size: AtomicI64::new(file_total_size),
244             rate_limiting: AtomicU64::new(0),
245             last_notify: AtomicU64::new(time),
246             client_manager,
247             running_result: Mutex::new(None),
248             timeout_tries: AtomicU32::new(0),
249             upload_resume: AtomicBool::new(upload_resume),
250         };
251         let background_notify = NotificationDispatcher::get_instance().register_task(&task);
252         task.background_notify = background_notify;
253         Ok(task)
254     }
255 
build_notify_data(&self) -> NotifyData256     pub(crate) fn build_notify_data(&self) -> NotifyData {
257         let vec = self.get_each_file_status();
258         NotifyData {
259             bundle: self.conf.bundle.clone(),
260             // `unwrap` for propagating panics among threads.
261             progress: self.progress.lock().unwrap().clone(),
262             action: self.conf.common_data.action,
263             version: self.conf.version,
264             each_file_status: vec,
265             task_id: self.conf.common_data.task_id,
266             uid: self.conf.common_data.uid,
267         }
268     }
269 
update_progress_in_database(&self)270     pub(crate) fn update_progress_in_database(&self) {
271         let mtime = self.status.lock().unwrap().mtime;
272         let reason = self.status.lock().unwrap().reason;
273         let progress = self.progress.lock().unwrap().clone();
274         let update_info = UpdateInfo {
275             mtime,
276             reason: reason.repr,
277             progress,
278             tries: self.tries.load(Ordering::SeqCst),
279             mime_type: self.mime_type(),
280         };
281         RequestDb::get_instance().update_task(self.task_id(), update_info);
282     }
283 
build_request_builder(&self) -> Result<RequestBuilder, HttpClientError>284     pub(crate) fn build_request_builder(&self) -> Result<RequestBuilder, HttpClientError> {
285         use ylong_http_client::async_impl::PercentEncoder;
286 
287         let url = self.conf.url.clone();
288         let url = match PercentEncoder::encode(url.as_str()) {
289             Ok(value) => value,
290             Err(e) => {
291                 error!("url percent encoding error is {:?}", e);
292                 return Err(e);
293             }
294         };
295 
296         let method = match self.conf.method.to_uppercase().as_str() {
297             "PUT" => "PUT",
298             "POST" => "POST",
299             "GET" => "GET",
300             _ => match self.conf.common_data.action {
301                 Action::Upload => {
302                     if self.conf.version == Version::API10 {
303                         "PUT"
304                     } else {
305                         "POST"
306                     }
307                 }
308                 Action::Download => "GET",
309                 _ => "",
310             },
311         };
312         let mut request = RequestBuilder::new().method(method).url(url.as_str());
313         for (key, value) in self.conf.headers.iter() {
314             request = request.header(key.as_str(), value.as_str());
315         }
316         Ok(request)
317     }
318 
clear_downloaded_file(&self) -> Result<(), std::io::Error>319     pub(crate) async fn clear_downloaded_file(&self) -> Result<(), std::io::Error> {
320         info!("task {} clear downloaded file", self.task_id());
321         let file = self.files.get_mut(0).unwrap();
322         file.set_len(0).await?;
323         file.seek(SeekFrom::Start(0)).await?;
324 
325         let mut progress_guard = self.progress.lock().unwrap();
326         progress_guard.common_data.total_processed = 0;
327         progress_guard.processed[0] = 0;
328 
329         Ok(())
330     }
331 
build_download_request(&self) -> Result<Request, TaskError>332     pub(crate) async fn build_download_request(&self) -> Result<Request, TaskError> {
333         let mut request_builder = self.build_request_builder()?;
334 
335         let file = self.files.get_mut(0).unwrap();
336 
337         let has_downloaded = file.metadata().await?.len();
338         let resume_download = has_downloaded > 0;
339         let require_range = self.require_range();
340 
341         let begins = self.conf.common_data.begins;
342         let ends = self.conf.common_data.ends;
343 
344         debug!(
345             "task {} build download request, resume_download: {}, require_range: {}",
346             self.task_id(),
347             resume_download,
348             require_range
349         );
350         match (resume_download, require_range) {
351             (true, false) => {
352                 let (builder, support_range) = self.support_range(request_builder);
353                 request_builder = builder;
354                 if support_range {
355                     request_builder =
356                         self.range_request(request_builder, begins + has_downloaded, ends);
357                 } else {
358                     self.clear_downloaded_file().await?;
359                 }
360             }
361             (false, true) => {
362                 request_builder = self.range_request(request_builder, begins, ends);
363             }
364             (true, true) => {
365                 let (builder, support_range) = self.support_range(request_builder);
366                 request_builder = builder;
367                 if support_range {
368                     request_builder =
369                         self.range_request(request_builder, begins + has_downloaded, ends);
370                 } else {
371                     return Err(TaskError::Failed(Reason::UnsupportedRangeRequest));
372                 }
373             }
374             (false, false) => {}
375         };
376 
377         let request = request_builder.body(Body::slice(self.conf.data.clone()))?;
378         Ok(request)
379     }
380 
range_request( &self, request_builder: RequestBuilder, begins: u64, ends: i64, ) -> RequestBuilder381     fn range_request(
382         &self,
383         request_builder: RequestBuilder,
384         begins: u64,
385         ends: i64,
386     ) -> RequestBuilder {
387         let range = if ends < 0 {
388             format!("bytes={begins}-")
389         } else {
390             format!("bytes={begins}-{ends}")
391         };
392         request_builder.header("Range", range.as_str())
393     }
394 
support_range(&self, mut request_builder: RequestBuilder) -> (RequestBuilder, bool)395     fn support_range(&self, mut request_builder: RequestBuilder) -> (RequestBuilder, bool) {
396         let progress_guard = self.progress.lock().unwrap();
397         let mut support_range = false;
398         if let Some(etag) = progress_guard.extras.get("etag") {
399             request_builder = request_builder.header("If-Range", etag.as_str());
400             support_range = true;
401         } else if let Some(last_modified) = progress_guard.extras.get("last-modified") {
402             request_builder = request_builder.header("If-Range", last_modified.as_str());
403             support_range = true;
404         }
405         if !support_range {
406             info!("task {} not support range", self.task_id());
407         }
408         (request_builder, support_range)
409     }
410 
get_file_info(&self, response: &Response) -> Result<(), TaskError>411     pub(crate) fn get_file_info(&self, response: &Response) -> Result<(), TaskError> {
412         let content_type = response.headers().get("content-type");
413         if let Some(mime_type) = content_type {
414             if let Ok(value) = mime_type.to_string() {
415                 *self.mime_type.lock().unwrap() = value;
416             }
417         }
418 
419         let content_length = response.headers().get("content-length");
420         if let Some(Ok(len)) = content_length.map(|v| v.to_string()) {
421             match len.parse::<i64>() {
422                 Ok(v) => {
423                     let mut progress = self.progress.lock().unwrap();
424                     progress.sizes = vec![v + progress.processed[0] as i64];
425                     self.file_total_size.store(v, Ordering::SeqCst);
426                     debug!("the download task content-length is {}", v);
427                 }
428                 Err(e) => {
429                     error!("convert string to i64 error: {:?}", e);
430                 }
431             }
432         } else {
433             error!("cannot get content-length of the task");
434             if self.conf.common_data.precise {
435                 return Err(TaskError::Failed(Reason::GetFileSizeFailed));
436             }
437         }
438         Ok(())
439     }
440 
handle_download_error( &self, err: HttpClientError, ) -> Result<(), TaskError>441     pub(crate) async fn handle_download_error(
442         &self,
443         err: HttpClientError,
444     ) -> Result<(), TaskError> {
445         if err.error_kind() != ErrorKind::UserAborted {
446             error!("Task {} {:?}", self.task_id(), err);
447         }
448         match err.error_kind() {
449             ErrorKind::Timeout => Err(TaskError::Failed(Reason::ContinuousTaskTimeout)),
450             // user triggered
451             ErrorKind::UserAborted => Err(TaskError::Waiting(TaskPhase::UserAbort)),
452             ErrorKind::BodyTransfer | ErrorKind::BodyDecode => {
453                 self.network_retry().await?;
454                 Err(TaskError::Failed(Reason::OthersError))
455             }
456             _ => {
457                 if format!("{}", err).contains("No space left on device") {
458                     Err(TaskError::Failed(Reason::InsufficientSpace))
459                 } else {
460                     Err(TaskError::Failed(Reason::OthersError))
461                 }
462             }
463         }
464     }
465 
466     #[cfg(feature = "oh")]
notify_response(&self, response: &Response)467     pub(crate) fn notify_response(&self, response: &Response) {
468         let tid = self.conf.common_data.task_id;
469         let version: String = response.version().as_str().into();
470         let status_code: u32 = response.status().as_u16() as u32;
471         let status_message: String;
472         if let Some(reason) = response.status().reason() {
473             status_message = reason.into();
474         } else {
475             error!("bad status_message {:?}", status_code);
476             return;
477         }
478         let headers = response.headers().clone();
479         debug!("notify_response");
480         self.client_manager
481             .send_response(tid, version, status_code, status_message, headers)
482     }
483 
require_range(&self) -> bool484     pub(crate) fn require_range(&self) -> bool {
485         self.conf.common_data.begins > 0 || self.conf.common_data.ends >= 0
486     }
487 
record_upload_response( &self, index: usize, response: Result<Response, HttpClientError>, )488     pub(crate) async fn record_upload_response(
489         &self,
490         index: usize,
491         response: Result<Response, HttpClientError>,
492     ) {
493         if let Ok(mut r) = response {
494             {
495                 let mut guard = self.progress.lock().unwrap();
496                 guard.extras.clear();
497                 for (k, v) in r.headers() {
498                     if let Ok(value) = v.to_string() {
499                         guard.extras.insert(k.to_string().to_lowercase(), value);
500                     }
501                 }
502             }
503 
504             let file = match self.body_files.get_mut(index) {
505                 Some(file) => file,
506                 None => return,
507             };
508             let _ = file.set_len(0).await;
509             loop {
510                 let mut buf = [0u8; 1024];
511                 let size = r.data(&mut buf).await;
512                 let size = match size {
513                     Ok(size) => size,
514                     Err(_e) => break,
515                 };
516 
517                 if size == 0 {
518                     break;
519                 }
520                 let _ = file.write_all(&buf[..size]).await;
521             }
522             // Makes sure all the data has been written to the target file.
523             let _ = file.sync_all().await;
524         }
525     }
526 
get_each_file_status(&self) -> Vec<EachFileStatus>527     pub(crate) fn get_each_file_status(&self) -> Vec<EachFileStatus> {
528         let mut vec = Vec::new();
529         // `unwrap` for propagating panics among threads.
530         let codes_guard = self.code.lock().unwrap();
531         for (i, file_spec) in self.conf.file_specs.iter().enumerate() {
532             let reason = *codes_guard.get(i).unwrap_or(&Reason::Default);
533             vec.push(EachFileStatus {
534                 path: file_spec.path.clone(),
535                 reason,
536                 message: reason.to_str().into(),
537             });
538         }
539         vec
540     }
541 
info(&self) -> TaskInfo542     pub(crate) fn info(&self) -> TaskInfo {
543         let status = self.status.lock().unwrap();
544         let progress = self.progress.lock().unwrap();
545         TaskInfo {
546             bundle: self.conf.bundle.clone(),
547             url: self.conf.url.clone(),
548             data: self.conf.data.clone(),
549             token: self.conf.token.clone(),
550             form_items: self.conf.form_items.clone(),
551             file_specs: self.conf.file_specs.clone(),
552             title: self.conf.title.clone(),
553             description: self.conf.description.clone(),
554             mime_type: {
555                 match self.conf.version {
556                     Version::API10 => match self.conf.common_data.action {
557                         Action::Download => match self.conf.headers.get("Content-Type") {
558                             None => "".into(),
559                             Some(v) => v.clone(),
560                         },
561                         Action::Upload => "multipart/form-data".into(),
562                         _ => "".into(),
563                     },
564                     Version::API9 => self.mime_type.lock().unwrap().clone(),
565                 }
566             },
567             progress: progress.clone(),
568             extras: progress.extras.clone(),
569             common_data: CommonTaskInfo {
570                 task_id: self.conf.common_data.task_id,
571                 uid: self.conf.common_data.uid,
572                 action: self.conf.common_data.action.repr,
573                 mode: self.conf.common_data.mode.repr,
574                 ctime: self.ctime,
575                 mtime: status.mtime,
576                 reason: status.reason.repr,
577                 gauge: self.conf.common_data.gauge,
578                 retry: self.conf.common_data.retry,
579                 tries: self.tries.load(Ordering::SeqCst),
580                 version: self.conf.version as u8,
581                 priority: self.conf.common_data.priority,
582             },
583         }
584     }
585 
notify_header_receive(&self)586     pub(crate) fn notify_header_receive(&self) {
587         if self.conf.version == Version::API9 && self.conf.common_data.action == Action::Upload {
588             let notify_data = self.build_notify_data();
589 
590             Notifier::header_receive(&self.client_manager, notify_data);
591         }
592     }
593 }
594 
595 #[derive(Clone, Debug)]
596 pub(crate) struct TaskStatus {
597     pub(crate) mtime: u64,
598     pub(crate) state: State,
599     pub(crate) reason: Reason,
600 }
601 
602 impl TaskStatus {
new(mtime: u64) -> Self603     pub(crate) fn new(mtime: u64) -> Self {
604         TaskStatus {
605             mtime,
606             state: State::Initialized,
607             reason: Reason::Default,
608         }
609     }
610 }
611 
check_file_specs(file_specs: &[FileSpec]) -> bool612 fn check_file_specs(file_specs: &[FileSpec]) -> bool {
613     const EL1: &str = "/data/storage/el1/base/";
614     const EL2: &str = "/data/storage/el2/base/";
615     const EL5: &str = "/data/storage/el5/base/";
616 
617     let mut result = true;
618     for (idx, spec) in file_specs.iter().enumerate() {
619         let path = &spec.path;
620         if !spec.is_user_file && !path.starts_with(EL1) && !path.starts_with(EL2) && !path.starts_with(EL5) {
621             error!("File path invalid - path: {}, idx: {}", path, idx);
622             result = false;
623             break;
624         }
625     }
626 
627     result
628 }
629 
check_config( config: &TaskConfig, #[cfg(feature = "oh")] system: SystemConfig, ) -> Result<(AttachedFiles, Client), ErrorCode>630 pub(crate) fn check_config(
631     config: &TaskConfig,
632     #[cfg(feature = "oh")] system: SystemConfig,
633 ) -> Result<(AttachedFiles, Client), ErrorCode> {
634     if !check_file_specs(&config.file_specs) {
635         return Err(ErrorCode::Other);
636     }
637     let files = AttachedFiles::open(config).map_err(|_| ErrorCode::FileOperationErr)?;
638     #[cfg(feature = "oh")]
639     let client = build_client(config, system).map_err(|_| ErrorCode::Other)?;
640 
641     #[cfg(not(feature = "oh"))]
642     let client = build_client(config).map_err(|_| ErrorCode::Other)?;
643     Ok((files, client))
644 }
645 
646 impl From<HttpClientError> for TaskError {
from(_value: HttpClientError) -> Self647     fn from(_value: HttpClientError) -> Self {
648         TaskError::Failed(Reason::BuildRequestFailed)
649     }
650 }
651 
652 impl From<io::Error> for TaskError {
from(_value: io::Error) -> Self653     fn from(_value: io::Error) -> Self {
654         TaskError::Failed(Reason::IoError)
655     }
656 }
657 
658 #[derive(Debug, PartialEq, Eq)]
659 pub enum TaskPhase {
660     NeedRetry,
661     UserAbort,
662     NetworkOffline,
663 }
664 
665 #[derive(Debug, PartialEq, Eq)]
666 pub enum TaskError {
667     Failed(Reason),
668     Waiting(TaskPhase),
669 }
670 
671 #[cfg(test)]
672 mod test {
673     use crate::task::request_task::change_upload_size;
674 
675     #[test]
ut_upload_size()676     fn ut_upload_size() {
677         assert_eq!(change_upload_size(0, -1, 30), 30);
678         assert_eq!(change_upload_size(10, -1, 30), 20);
679         assert_eq!(change_upload_size(0, 10, 30), 11);
680         assert_eq!(change_upload_size(10, 10, 100), 1);
681         assert_eq!(change_upload_size(0, 30, 30), 30);
682         assert_eq!(change_upload_size(0, 0, 0), 0);
683         assert_eq!(change_upload_size(10, 9, 100), 100);
684     }
685 }
686