1/* 2* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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*/ 15import mediaLibrary from '@ohos.multimedia.mediaLibrary' 16import fs from '@ohos.file.fs' 17import request from '@ohos.request' 18import common from '@ohos.app.ability.common' 19import Logger from '../util/Logger' 20import { UploadFile } from '../feature/UploadFile' 21import emitter from '@ohos.events.emitter' 22import CostTimeCompute from '../feature/CostTimeCompute' 23import { getConfig, ConfigType } from '../util/ConfigUtil' 24 25const TAG: string = '[UploadController]' 26 27//控制下载任务 28//展示下载进度,展示下载控制器 29@Preview 30@Component 31export default struct UploadController { 32 @State imageUrl: string = ""; 33 @State isDownloading: boolean = false; 34 @State isError: boolean = false; 35 @State errorMessage: string = ""; 36 @State @Watch('onCompleteChanged') isCompleted: boolean = false; 37 @State progress: number = 0; 38 @State total: number = 1; 39 @Link isCompletedArr: Array<boolean> 40 uploadUrl: string = "" 41 innerEvent = { 42 eventId: 1 43 }; 44 uploadTask: request.UploadTask; 45 46 // 剩余时间计算 47 @State costTimeStr: string = "1s"; 48 @State lastTimeStr: string = ""; 49 costTimeCompute: CostTimeCompute = null; 50 51 onCompleteChanged(isCompleted: boolean) { 52 if (isCompleted) { 53 Logger.info(TAG, "上传任务,onCompleteChanged();imageUrl=" + this.imageUrl) 54 if (this.isCompletedArr == null) { 55 this.isCompletedArr = []; 56 } 57 let i = this.isCompletedArr.push(true) 58 } 59 } 60 61 async aboutToAppear() { 62 this.uploadUrl = await getConfig(getContext(this), ConfigType.uploadUrl) 63 Logger.info(TAG, 'register publish event!') 64 emitter.on(this.innerEvent, (eventData) => { 65 Logger.info(TAG, `receive publish event!=${JSON.stringify(eventData)}`) 66 this.startUpload(this.imageUrl); 67 }); 68 //定义计算器 69 this.costTimeCompute = new CostTimeCompute( 70 (str) => { 71 this.lastTimeStr = str; 72 }, 73 () => { 74 return this.progress; 75 }, 76 () => { 77 return this.total; 78 }, 79 (str) => { 80 this.costTimeStr = str; 81 } 82 , true 83 ); 84 } 85 86 aboutToDisappear() { 87 emitter.off(this.innerEvent.eventId); 88 if (this.isDownloading) { 89 this.cancelUpload(); 90 } 91 } 92 93 async startUpload(url: string): Promise<void> { 94 if (this.isDownloading || this.isCompleted) { 95 return; 96 } 97 this.isError = false; 98 this.isDownloading = true; 99 this.copyImage(url) 100 this.costTimeCompute.startCostTime(); 101 } 102 103 async cancelUpload() { 104 if (this.uploadTask == null || !this.isDownloading) { 105 return; 106 } 107 this.removeListener(); 108 await this.uploadTask.delete(); 109 this.uploadTask = null; 110 111 this.isDownloading = false; 112 this.progress = 0; 113 this.total = 1; 114 this.costTimeCompute.cancelCostTime(); 115 } 116 117 async restartUpload() { 118 await this.cancelUpload(); 119 this.startUpload(this.imageUrl); 120 } 121 122 async copyImage(url: string): Promise<void> { 123 Logger.info(TAG, `copyImage url = ${url}`) 124 // 将选中图片存入沙箱路径 125 let id = url.slice(url.lastIndexOf('/')+1); 126 Logger.info(TAG, `this.id = ${id}`) 127 let mContext: common.Context = getContext(this) as common.Context 128 let media = mediaLibrary.getMediaLibrary(mContext) 129 Logger.info(TAG, `this.media = ${JSON.stringify(media)}`) 130 131 let file = fs.openSync(url, fs.OpenMode.READ_WRITE); 132 let fd = file.fd; 133 Logger.info(TAG, 'file fd: ' + file); 134 135 // upload可访问的沙箱路径:data/app/el2/100/base/com.example.myapplication/haps/entry/cache/ 136 let imagePath = `${mContext.filesDir.split('files')[0]}cache/${new Date().getTime().toString()}.jpg` 137 Logger.info(TAG, `this.imagePath = ${JSON.stringify(imagePath)}`) 138 try { 139 fs.copyFileSync(fd, imagePath) 140 await this.isUploadImage(imagePath) 141 } catch (err) { 142 Logger.info(TAG, `this.err = ${err}`) 143 } 144 } 145 146 async isUploadImage(imagePath: string): Promise<void> { 147 // 转成可上传的图片格式进行上传 148 let file = new UploadFile() 149 file.uri = `internal://cache/${imagePath.split('cache/')[1]}` 150 Logger.info(TAG, `file.uri = ${file.uri}`) 151 file.filename = imagePath.split('cache/')[1] 152 Logger.info(TAG, `file.filename = ${file.filename}`) 153 file.name = imagePath.split('cache/')[1].split('.')[0] 154 Logger.info(TAG, `file.name = ${file.name}`) 155 file.type = imagePath.split('.')[1] 156 Logger.info(TAG, `file.type = ${file.type}`) 157 Logger.debug("upload path=" + this.uploadUrl); 158 let uploadConfig = { 159 url: this.uploadUrl, 160 header: { key1: 'value1', key2: 'value2' }, 161 method: 'POST', 162 files: [{ filename: file.filename, name: file.name, uri: file.uri, type: file.type }], 163 data: [{ name: 'name123', value: '123' }], 164 } 165 Logger.info(TAG, `uploadConfig = ${JSON.stringify(uploadConfig)}`) 166 let uploadContext: common.BaseContext = getContext(this) as common.BaseContext 167 this.uploadTask = await request.uploadFile(uploadContext, uploadConfig) 168 this.addListener(); 169 } 170 171 addListener() { 172 if (this.uploadTask == null) { 173 return; 174 } 175 this.uploadTask.on('complete', async (taskStates) => { 176 await this.uploadTask.delete() 177 Logger.info(TAG, `uploadSuccess`) 178 this.isDownloading = false; 179 this.isCompleted = true; 180 this.costTimeCompute.cancelCostTime(); 181 }) 182 this.uploadTask.on('progress', (uploadSize: number, uploadTotal: number) => { 183 this.progress = uploadSize; 184 this.total = uploadTotal; 185 }) 186 this.uploadTask.on('fail', (err: Array<request.TaskState>) => { 187 this.isDownloading = false; 188 this.isError = true; 189 this.costTimeCompute.cancelCostTime(); 190 this.progress = 0; 191 try { 192 this.errorMessage = err.pop().message; 193 } catch (err) { 194 Logger.error('获取err message报错:' + err) 195 } 196 }) 197 } 198 199 removeListener() { 200 if (this.uploadTask == null) { 201 return; 202 } 203 this.uploadTask.off("complete"); 204 this.uploadTask.off("progress"); 205 this.uploadTask.off("fail"); 206 } 207 208 getTimeLastTip() { 209 let context: common.Context = getContext(this) as common.Context; 210 let lastTime = context.resourceManager.getStringSync($r('app.string.tip_last_time2')); 211 let computingStr = context.resourceManager.getStringSync($r('app.string.tip_computing2')); 212 return lastTime + ((this.lastTimeStr != null && this.lastTimeStr.length > 0) ? this.lastTimeStr : computingStr); 213 } 214 215 getCostTimeStr() { 216 let context: common.Context = getContext(this) as common.Context; 217 let costTimeTip = context.resourceManager.getStringSync($r("app.string.tip_cost_time")); 218 return costTimeTip + this.costTimeStr 219 } 220 221 build() { 222 Stack({ alignContent: Alignment.Center }) { 223 RelativeContainer() { 224 //背景图 225 Stack() { 226 if (!this.isCompleted) { 227 //蒙层和按钮 228 Stack({ alignContent: Alignment.Center }) { 229 if (this.isError) { 230 //失败状态 231 RelativeContainer() { 232 Image($r('app.media.ic_public_reset')) 233 .fillColor($r('app.color.progress_color')) 234 .width('45%') 235 .height('45%') 236 .alignRules({ 237 center: { anchor: "__container__", align: VerticalAlign.Center }, 238 middle: { anchor: "__container__", align: HorizontalAlign.Center } 239 }) 240 .id('restart') 241 .onClick(() => { 242 this.restartUpload(); 243 }) 244 245 Text($r('app.string.upload_error_message')) 246 .fontSize($r('app.float.upload_controller_font_size')) 247 .fontColor($r('app.color.upload_error_color')) 248 .margin({ top: $r('app.float.upload_time_margin_top') }) 249 .alignRules({ 250 top: { anchor: "restart", align: VerticalAlign.Bottom }, 251 middle: { anchor: "__container__", align: HorizontalAlign.Center } 252 }) 253 .id('error') 254 }.width('100%') 255 .height('100%') 256 } else if (this.isDownloading) { 257 //下载中状态 258 RelativeContainer() { 259 Progress({ value: this.progress, total: this.total, type: ProgressType.Ring }) 260 .width('45%') 261 .height('45%') 262 .style({ 263 strokeWidth: $r('app.float.progress_stroke_width') 264 }) 265 .backgroundColor($r('app.color.progress_background')) 266 .color($r('app.color.progress_color')) 267 .alignRules({ 268 center: { anchor: "__container__", align: VerticalAlign.Center }, 269 middle: { anchor: "__container__", align: HorizontalAlign.Center } 270 }) 271 .id('progress') 272 273 Text(this.getTimeLastTip()) 274 .fontSize($r('app.float.upload_controller_font_size')) 275 .fontColor($r('app.color.color_cover')) 276 .margin({ top: $r('app.float.upload_time_margin_top') }) 277 .alignRules({ 278 top: { anchor: "progress", align: VerticalAlign.Bottom }, 279 middle: { anchor: "__container__", align: HorizontalAlign.Center } 280 }) 281 .id('time') 282 }.width('100%') 283 .height('100%') 284 } else { 285 //准备状态 286 Image($r('app.media.ic_public_upload')) 287 .width('50%') 288 .aspectRatio(1) 289 .fillColor($r('app.color.color_cover')) 290 .onClick(() => { 291 this.startUpload(this.imageUrl) 292 }) 293 } 294 } 295 .backgroundColor($r('app.color.color_blend')) 296 .width('100%') 297 .height('100%') 298 } 299 } 300 .alignRules({ 301 center: { anchor: "__container__", align: VerticalAlign.Center }, 302 middle: { anchor: "__container__", align: HorizontalAlign.Center } 303 }) 304 .backgroundImage(this.imageUrl, ImageRepeat.NoRepeat) 305 .backgroundImageSize({ width: '100%', height: '100%' }) 306 .width('100%') 307 .aspectRatio(1) 308 .margin($r('app.float.add_picture_margin_background')) 309 .id('background') 310 311 //删除按钮 312 if (this.isDownloading) { 313 Stack({ alignContent: Alignment.TopEnd }) { 314 Image($r('app.media.ic_public_list_deleted')) 315 .height($r('app.float.add_picture_cancel_ic_width')) 316 .width($r('app.float.add_picture_cancel_ic_width')) 317 } 318 .width($r('app.float.add_picture_cancel_click_area')) 319 .height($r('app.float.add_picture_cancel_click_area')) 320 .id('ic_deleted') 321 .alignRules({ 322 top: { anchor: "__container__", align: VerticalAlign.Top }, 323 right: { anchor: "__container__", align: HorizontalAlign.End } 324 }) 325 .onClick(() => { 326 this.cancelUpload(); 327 }) 328 } 329 } 330 .height('100%') 331 .width('100%') 332 } 333 .width('100%') 334 .aspectRatio(1) 335 } 336}