1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import common from '@ohos.app.ability.common'; 17import request from '@ohos.request'; 18import promptAction from '@ohos.promptAction'; 19import { urlUtils } from '../utils/UrlUtils'; 20import { logger } from '../utils/Logger'; 21import { MediaUtils } from '../utils/MediaUtils'; 22import { BackgroundTaskState, UPLOAD_TOKEN, TOAST_BOTTOM, TASK_MAX } from '../utils/Constants'; 23 24const TAG: string = 'RequestUpload'; 25const HEADER: Record<string, string> = { 'Content-Type': 'multipart/form-data' }; 26 27class Upload { 28 private mediaUtils: MediaUtils = new MediaUtils(); 29 private config: request.agent.Config = { 30 action: request.agent.Action.UPLOAD, 31 headers: HEADER, 32 url: '', 33 mode: request.agent.Mode.FOREGROUND, 34 method: 'POST', 35 title: 'upload', 36 network: request.agent.Network.ANY, 37 data: [], 38 token: UPLOAD_TOKEN 39 } 40 private context: common.UIAbilityContext | undefined = undefined; 41 private uploadTask: request.agent.Task | undefined = undefined; 42 private backgroundTask: request.agent.Task | undefined = undefined; 43 private waitList: Array<string> = []; 44 45 constructor() { 46 setInterval(() => { 47 this.flushBackgroundTask() 48 }, 2000); 49 } 50 51 getFileName(filePath: string): string { 52 const lastSlashIndex = filePath.lastIndexOf('/'); 53 if (lastSlashIndex === -1) { 54 return filePath; 55 } 56 return filePath.substring(lastSlashIndex + 1); 57 } 58 59 async uploadFilesBackground(fileUris: Array<string>): Promise<void> { 60 logger.info(TAG, `uploadFilesBackground:uploadFiles begin, ${JSON.stringify(fileUris)}`); 61 this.context = getContext(this) as common.UIAbilityContext; 62 if (fileUris.length === 0) { 63 return; 64 } 65 fileUris.forEach((item: string) => { 66 this.waitList.push(item); 67 }); 68 } 69 70 async flushBackgroundTask() { 71 logger.info(TAG, 'flushBackgroundTask:uploadFiles begin'); 72 let tasks = await request.agent.search({ 73 state: request.agent.State.RUNNING 74 }); 75 let state = AppStorage.get<number>('backTaskState'); 76 if (state === BackgroundTaskState.RUNNING) { 77 if (tasks.length < TASK_MAX && this.waitList.length > 0) { 78 this.createBackgroundTask(this.waitList); 79 this.waitList = []; 80 } else { 81 if (this.backgroundTask === undefined || tasks.indexOf(this.backgroundTask.tid) === -1) { 82 AppStorage.setOrCreate('backTaskState', BackgroundTaskState.NONE); 83 this.backgroundTask = undefined; 84 } 85 } 86 } 87 } 88 89 async createBackgroundTask(fileUris: Array<string>) { 90 logger.info(TAG, `createBackgroundTask:uploadFiles begin, ${JSON.stringify(fileUris)}`); 91 if (this.context === undefined) { 92 return; 93 } 94 this.config.url = await urlUtils.getUrl(this.context); 95 this.config.data = await this.getFilesAndData(this.context.cacheDir, fileUris); 96 this.config.mode = request.agent.Mode.BACKGROUND; 97 try { 98 this.backgroundTask = await request.agent.create(this.context, this.config); 99 await this.backgroundTask.start(); 100 let state = AppStorage.get<number>('backTaskState'); 101 if (state === BackgroundTaskState.PAUSE) { 102 await this.backgroundTask.pause(); 103 } 104 logger.info(TAG, `createBackgroundTask success`); 105 } catch (err) { 106 logger.error(TAG, `task err, err = ${JSON.stringify(err)}`); 107 } 108 } 109 110 async uploadFiles(fileUris: Array<string>, callback: (progress: number, isSucceed: boolean) => void): Promise<void> { 111 logger.info(TAG, `uploadFiles begin, ${JSON.stringify(fileUris)}`); 112 if (fileUris.length === 0) { 113 return; 114 } 115 // 查询到存在正在执行的上传任务,提示并返回 116 let tasks = await request.agent.search({ 117 state: request.agent.State.RUNNING, 118 action: request.agent.Action.UPLOAD, 119 mode: request.agent.Mode.FOREGROUND 120 }); 121 if (tasks.length > 0) { 122 promptAction.showToast({ message: $r('app.string.have_upload_task_tips'), bottom: TOAST_BOTTOM }); 123 return; 124 } 125 let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; 126 this.config.data = await this.getFilesAndData(context.cacheDir, fileUris); 127 logger.info(TAG, `config data:${JSON.stringify(this.config.data)}`); 128 this.config.url = await urlUtils.getUrl(context); 129 logger.info(TAG, `config url:${JSON.stringify(this.config.url)}`); 130 this.config.mode = request.agent.Mode.FOREGROUND; 131 try { 132 this.uploadTask = await request.agent.create(context, this.config); 133 this.uploadTask.on('progress', (progress: request.agent.Progress) => { 134 logger.info(TAG, `progress, progress = ${progress.processed} ${progress.state}`); 135 let processed = Number(progress.processed.toString()).valueOf(); 136 let size = progress.sizes[0]; 137 let process: number = Math.floor(processed / size * 100); 138 if (process < 100) { 139 callback(process, false); 140 } 141 }); 142 this.uploadTask.on('completed', (progress: request.agent.Progress) => { 143 logger.info(TAG, `complete, progress = ${progress.processed} ${progress.state}`); 144 callback(100, true); 145 this.cancelTask(); 146 }); 147 this.uploadTask.on('failed', async (progress: request.agent.Progress) => { 148 if (this.uploadTask) { 149 let taskInfo = await request.agent.touch(this.uploadTask.tid, UPLOAD_TOKEN); 150 logger.info(TAG, `fail, resean = ${taskInfo.reason}, faults = ${JSON.stringify(taskInfo.faults)}`); 151 } 152 callback(100, false); 153 this.cancelTask(); 154 }); 155 await this.uploadTask.start(); 156 } catch (err) { 157 logger.error(TAG, `task err, err = ${JSON.stringify(err)}`); 158 callback(100, false); 159 } 160 } 161 162 async uploadFileChunk(fileUris: Array<string>, begin: number, end: number, callback: (progress: number, isSucceed: boolean) => void): Promise<void> { 163 logger.info(TAG, `uploadFileChunk begin,${fileUris}, begin:${begin}, end:${end}`); 164 // 查询到存在正在执行的上传任务,提示并返回 165 let tasks = await request.agent.search({ 166 state: request.agent.State.RUNNING, 167 action: request.agent.Action.UPLOAD, 168 mode: request.agent.Mode.FOREGROUND 169 }); 170 if (tasks.length > 0) { 171 promptAction.showToast({ message: $r('app.string.have_upload_task_tips'), bottom: TOAST_BOTTOM }); 172 return; 173 } 174 let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; 175 let config: request.agent.Config = { 176 action: request.agent.Action.UPLOAD, 177 headers: HEADER, 178 url: await urlUtils.getUrl(context), 179 mode: request.agent.Mode.FOREGROUND, 180 method: 'POST', 181 title: 'upload', 182 network: request.agent.Network.ANY, 183 data: await this.getFilesAndData(context.cacheDir, fileUris), 184 token: UPLOAD_TOKEN, 185 index: 0, // 指定片段上传的文件 186 begins: begin, 187 ends: end 188 }; 189 190 try { 191 this.uploadTask = await request.agent.create(context, config); 192 this.uploadTask.on('progress', (progress: request.agent.Progress) => { 193 let processed = Number(progress.processed.toString()).valueOf(); 194 let size = progress.sizes[progress.index]; 195 if (progress.index === 0) { 196 if (end === -1) { 197 size -= begin; 198 } else { 199 size = end - begin + 1; 200 } 201 } 202 logger.info(TAG, `progress, index:${progress.index}, progressed = ${progress.processed}, size=${size}`); 203 let process: number = Math.floor(processed / size * 100); 204 if (process < 100) { 205 callback(process, false); 206 } 207 }); 208 this.uploadTask.on('completed', (progress: request.agent.Progress) => { 209 logger.info(TAG, `complete, progress = ${progress.processed} ${progress.state}`); 210 callback(100, true); 211 this.cancelTask(); 212 }); 213 this.uploadTask.on('failed', async (progress: request.agent.Progress) => { 214 if (this.uploadTask) { 215 let taskInfo = await request.agent.touch(this.uploadTask.tid, UPLOAD_TOKEN); 216 logger.info(TAG, `fail, resean = ${taskInfo.reason}, faults = ${JSON.stringify(taskInfo.faults)}`); 217 } 218 callback(100, false); 219 this.cancelTask(); 220 }) 221 await this.uploadTask.start(); 222 } catch (err) { 223 logger.error(TAG, `task err, err = ${JSON.stringify(err)}`); 224 callback(100, false); 225 } 226 } 227 228 async cancelTask() { 229 logger.info(TAG, 'cancelTask'); 230 if (this.uploadTask === undefined) { 231 return; 232 } 233 try { 234 this.uploadTask.off('progress'); 235 this.uploadTask.off('failed'); 236 this.uploadTask.off('completed'); 237 await this.uploadTask.stop(); 238 await request.agent.remove(this.uploadTask.tid); 239 } catch (err) { 240 logger.info(TAG, `deleteTask fail,err= ${JSON.stringify(err)}`); 241 } 242 this.uploadTask = undefined; 243 } 244 245 async pauseOrResume() { 246 logger.info(TAG, 'pauseOrResume'); 247 let state = AppStorage.get<number>('backTaskState'); 248 if (state === BackgroundTaskState.RUNNING) { 249 await this.pause(); 250 AppStorage.setOrCreate('backTaskState', BackgroundTaskState.PAUSE); 251 } else if (state === BackgroundTaskState.PAUSE) { 252 await this.resume(); 253 AppStorage.setOrCreate('backTaskState', BackgroundTaskState.RUNNING); 254 } else { 255 logger.info(TAG, 'this task state is error'); 256 } 257 } 258 259 async pause() { 260 logger.info(TAG, 'pause'); 261 if (this.backgroundTask === undefined) { 262 return; 263 } 264 try { 265 await this.backgroundTask.pause(); 266 } catch (err) { 267 logger.info(TAG, `pause fail,err= ${JSON.stringify(err)}`); 268 } 269 } 270 271 async resume() { 272 logger.info(TAG, 'resume'); 273 if (this.backgroundTask === undefined) { 274 return; 275 } 276 try { 277 await this.backgroundTask.resume(); 278 } catch (err) { 279 logger.info(TAG, `resume fail,err= ${JSON.stringify(err)}`); 280 } 281 } 282 283 private async getFilesAndData(cacheDir: string, fileUris: Array<string>): Promise<Array<request.agent.FormItem>> { 284 logger.info(TAG, `getFilesAndData begin`); 285 let files: Array<request.agent.FormItem> = []; 286 for (let i = 0; i < fileUris.length; i++) { 287 logger.info(TAG, `getFile fileUri = ${fileUris[i]}`); 288 let imagePath = fileUris[i]; 289 logger.info(TAG, `getFilesAndData imagePath: ${JSON.stringify(imagePath)}`); 290 let fileName = this.getFileName(imagePath); 291 let file: request.agent.FormItem = { 292 name: 'createTest', 293 value: { 294 filename: fileName, 295 mimeType: 'application/octet-stream', 296 path: imagePath, 297 } 298 } 299 logger.info(TAG, `getFile file = ${JSON.stringify(file)}`); 300 files.push(file); 301 } 302 logger.info(TAG, `getFilesAndData ${JSON.stringify(files)}`); 303 return files; 304 } 305} 306 307export const requestUpload = new Upload();