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 type common from '@ohos.app.ability.common'; 17import update from '@ohos.update'; 18import { PACKAGE_NAME, UpdateState, UpgradeCallResult, } from '../const/update_const'; 19import type { BusinessError, OtaStatus, UpgradeData} from '../const/update_const'; 20import { LogUtils } from '../util/LogUtils'; 21import { UpdateUtils } from '../util/UpdateUtils'; 22 23/** 24 * 方法超时控制装饰器 25 * 26 * @param timeout 超时事件ms 27 */ 28export function enableTimeOutCheck<T>(timeout?: number): MethodDecorator { 29 const TIME = 30000; 30 let realTimeout: number = timeout ?? TIME; 31 return function inner(target: Object, propertyKey: string, descriptor: PropertyDescriptor): void { 32 const original = descriptor.value; 33 descriptor.value = function (...args): Promise<unknown> { 34 return new Promise((resolve, reject) => { 35 let upgradeData: UpgradeData<T> = { 36 callResult: UpgradeCallResult.OK, 37 }; 38 const requestTimeout = setTimeout(() => { 39 upgradeData.callResult = UpgradeCallResult.TIME_OUT; 40 resolve(upgradeData); 41 }, realTimeout); 42 let result: Promise<T>; 43 try { 44 result = original.call(this, ...args); 45 } catch (error) { 46 LogUtils.error('UpdateManager', 'error: ' + JSON.stringify(error)); 47 result = null; 48 } 49 if (!result) { 50 clearTimeout(requestTimeout); 51 upgradeData.callResult = UpgradeCallResult.ERROR; 52 resolve(upgradeData); // 不处理错误 53 return; 54 } 55 result.then(innerRes => { 56 clearTimeout(requestTimeout); 57 resolve(innerRes); 58 }).catch(err => { 59 LogUtils.error('UpdateManager', 'err: ' + JSON.stringify(err)); 60 clearTimeout(requestTimeout); 61 upgradeData.callResult = UpgradeCallResult.ERROR; 62 upgradeData.error = err; 63 resolve(upgradeData); // 不处理错误 64 }); 65 }); 66 }; 67 }; 68} 69 70export interface IUpdate { 71 bind(subType: number, callback : Function): void; 72 getOtaStatus(): Promise<UpgradeData<OtaStatus>>; 73 getNewVersion(): Promise<UpgradeData<update.NewVersionInfo>>; 74 getNewVersionDescription(descVersionDigest: string, descFormat: update.DescriptionFormat, 75 descLanguage: string): Promise<UpgradeData<Array<update.ComponentDescription>>>; 76 getCurrentVersionDescription(descFormat: update.DescriptionFormat, 77 descLanguage: string): Promise<UpgradeData<Array<update.ComponentDescription>>>; 78 checkNewVersion(): Promise<UpgradeData<update.CheckResult>>; 79 upgrade(upgradeVersionDigest: string, upgradeOrder: number): Promise<void>; 80 download(downloadVersionDigest: string, downloadNetwork: number, downloadOrder: number): Promise<void>; 81 cancel(): void; 82 getCurrentVersionInfo(): Promise<UpgradeData<update.CurrentVersionInfo>>; 83} 84 85/** 86 * 升级接口管理类 87 * 88 * @since 2022-06-05 89 */ 90export class UpdateManager implements IUpdate { 91 private otaUpdater: update.Updater; 92 93 public constructor() { 94 } 95 96 /** 97 * 绑定DUE 98 * 99 * @param subType 升级类型 100 * @param callback 回调 101 */ 102 bind(subType: number, callback: update.UpgradeTaskCallback): void { 103 let upgradeInfo: update.UpgradeInfo = { 104 upgradeApp: PACKAGE_NAME, 105 businessType: { 106 vendor: update.BusinessVendor.PUBLIC, 107 subType: subType 108 } 109 }; 110 111 try { 112 this.otaUpdater = update.getOnlineUpdater(upgradeInfo); 113 let eventClassifyInfo: update.EventClassifyInfo = { eventClassify: 0x01000000, extraInfo: '' }; 114 this.otaUpdater?.on(eventClassifyInfo, callback); 115 } catch (error) { 116 LogUtils.error('UpdateManager', 'otaUpdater init fail ' + JSON.stringify(error)); 117 } 118 } 119 120 /** 121 * 取升级状态 122 * 123 * @return resolve 状态/reject 错误信息 124 */ 125 @enableTimeOutCheck() 126 async getOtaStatus(): Promise<UpgradeData<OtaStatus>> { 127 return new Promise((resolve, reject) => { 128 this.otaUpdater?.getTaskInfo().then((result: update.TaskInfo) => { 129 this.log(`getOtaStatus result is ${JSON.stringify(result)}`); 130 let upgradeData: UpgradeData<OtaStatus> = { 131 callResult: UpgradeCallResult.OK 132 }; 133 let taskStatus = result?.existTask ? result?.taskBody?.status : UpdateState.INIT; 134 let otaStatus: OtaStatus = { 135 status: taskStatus, 136 percent: result?.taskBody?.progress ?? 0, 137 endReason: result?.taskBody?.errorMessages?.[0]?.errorCode?.toString() 138 }; 139 upgradeData.data = otaStatus; 140 resolve(upgradeData); 141 }).catch((err: BusinessError) => { 142 this.logError(`getOtaStatus error is ${JSON.stringify(err)}`); 143 let upgradeData: UpgradeData<OtaStatus> = { 144 callResult: UpgradeCallResult.ERROR, 145 error: err 146 }; 147 resolve(upgradeData); 148 }); 149 }); 150 } 151 152 /** 153 * 从due数据库取新版本信息 154 * 155 * @return resolve 新版本信息/reject 错误信息 156 */ 157 @enableTimeOutCheck() 158 async getNewVersion(): Promise<UpgradeData<update.NewVersionInfo>> { 159 return new Promise((resolve, reject) => { 160 this.otaUpdater?.getNewVersionInfo().then((result: update.NewVersionInfo) => { 161 this.log('getNewVersion result:' + JSON.stringify(result)); 162 let upgradeData: UpgradeData<update.NewVersionInfo> = { 163 callResult: UpgradeCallResult.OK, 164 data: result 165 }; 166 resolve(upgradeData); 167 }).catch((err: BusinessError) => { 168 this.logError('getNewVersion result:' + JSON.stringify(err)); 169 let upgradeData: UpgradeData<update.NewVersionInfo> = { 170 callResult: UpgradeCallResult.ERROR, 171 error: err 172 }; 173 resolve(upgradeData); 174 }); 175 }); 176 } 177 178 /** 179 * 获取新版本描述文件 180 * 181 * @param descVersionDigest 版本摘要 182 * @param descFormat 描述文件格式 183 * @param descLanguage 描述文件语言 184 * @return 新版本描述文件 185 */ 186 @enableTimeOutCheck() 187 async getNewVersionDescription(descVersionDigest: string, descFormat: update.DescriptionFormat, 188 descLanguage: string): Promise<UpgradeData<Array<update.ComponentDescription>>> { 189 let versionDigestInfo: update.VersionDigestInfo = { 190 versionDigest: descVersionDigest, // 检测结果中的版本摘要信息 191 }; 192 let descriptionOptions: update.DescriptionOptions = { 193 format: descFormat, 194 language: descLanguage 195 }; 196 return new Promise((resolve, reject) => { 197 this.otaUpdater?.getNewVersionDescription(versionDigestInfo, 198 descriptionOptions).then((result: Array<update.ComponentDescription>) => { 199 this.log('getNewVersionDescription result:' + JSON.stringify(result)); 200 let upgradeData: UpgradeData<Array<update.ComponentDescription>> = { 201 callResult: UpgradeCallResult.OK, 202 data: result 203 }; 204 resolve(upgradeData); 205 }).catch((err: BusinessError) => { 206 this.logError('getNewVersionDescription err:' + JSON.stringify(err)); 207 let upgradeData: UpgradeData<Array<update.ComponentDescription>> = { 208 callResult: UpgradeCallResult.ERROR, 209 error: err 210 }; 211 resolve(upgradeData); 212 }); 213 }); 214 } 215 216 /** 217 * 获取当前版本升级日志 218 * 219 * @param descFormat 描述文件格式 220 * @param descLanguage 描述文件语言 221 * @return 当前版本描述文件 222 */ 223 @enableTimeOutCheck() 224 async getCurrentVersionDescription(descFormat: update.DescriptionFormat, 225 descLanguage: string): Promise<UpgradeData<Array<update.ComponentDescription>>> { 226 let options: update.DescriptionOptions = { 227 format: descFormat, 228 language: descLanguage 229 }; 230 return new Promise((resolve, reject) => { 231 this.otaUpdater?.getCurrentVersionDescription(options, (err, result) => { 232 this.log('getCurrentVersionDescription result:' + JSON.stringify(result)); 233 let upgradeData: UpgradeData<Array<update.ComponentDescription>> = { 234 callResult: UpgradeCallResult.OK, 235 data: result, 236 error: { 237 data: [{ errorCode: err?.data?.[0]?.errorCode }] 238 } 239 }; 240 if (!UpdateUtils.isSuccessCallback(result, err)) { 241 this.logError('getCurrentVersionDescription error is ${JSON.stringify(err)}'); 242 upgradeData.callResult = UpgradeCallResult.ERROR; 243 } 244 resolve(upgradeData); 245 }); 246 }); 247 } 248 249 /** 250 * 从服务器取搜索新版本 251 * 252 * @return resolve 新版本信息/reject 错误信息 253 */ 254 @enableTimeOutCheck() 255 async checkNewVersion(): Promise<UpgradeData<update.CheckResult>> { 256 return new Promise((resolve, reject) => { 257 this.otaUpdater?.checkNewVersion().then((result: update.CheckResult) => { 258 this.log('checkNewVersion result:' + JSON.stringify(result)); 259 let upgradeData: UpgradeData<update.CheckResult> = { 260 callResult: UpgradeCallResult.OK, 261 data: result, 262 }; 263 if (!result?.isExistNewVersion || !result?.newVersionInfo) { 264 upgradeData.callResult = UpgradeCallResult.ERROR; 265 } 266 resolve(upgradeData); 267 }).catch((err: BusinessError) => { 268 this.logError('checkNewVersion err:' + JSON.stringify(err)); 269 let upgradeData: UpgradeData<update.CheckResult> = { 270 callResult: UpgradeCallResult.ERROR, 271 error: err 272 }; 273 resolve(upgradeData); 274 }); 275 }); 276 } 277 278 /** 279 * 升级 280 * 281 * @param upgradeVersionDigest 版本摘要 282 * @param upgradeOrder 升级命令 283 * @return 调用结果 284 */ 285 upgrade(upgradeVersionDigest: string, upgradeOrder: number): Promise<void> { 286 return new Promise((resolve, reject) => { 287 let versionDigestInfo: update.VersionDigestInfo = { 288 versionDigest: upgradeVersionDigest 289 }; 290 let upgradeOptions: update.UpgradeOptions = { 291 order: upgradeOrder 292 }; 293 this.otaUpdater?.upgrade(versionDigestInfo, upgradeOptions).then(() => { 294 resolve(); 295 }).catch((err: BusinessError) => { 296 this.logError('upgrade err:' + JSON.stringify(err)); 297 reject(err); 298 }); 299 }); 300 } 301 302 /** 303 * 下载 304 * 305 * @param upgradeVersionDigest 版本摘要 306 * @param downloadNetwork 下载网络 307 * @param upgradeOrder 下载命令 308 * @return 调用结果 309 */ 310 download(downloadVersionDigest: string, downloadNetwork: number, downloadOrder: number): Promise<void> { 311 return new Promise((resolve, reject) => { 312 let versionDigestInfo: update.VersionDigestInfo = { 313 versionDigest: downloadVersionDigest 314 }; 315 let downloadOptions: update.DownloadOptions = { 316 allowNetwork: downloadNetwork, 317 order: downloadOrder 318 }; 319 this.otaUpdater?.download(versionDigestInfo, downloadOptions).then(() => { 320 this.log('download succeeded.'); 321 resolve(); 322 }).catch((err: BusinessError) => { 323 this.logError('download err:' + JSON.stringify(err)); 324 reject(err); 325 }); 326 }); 327 } 328 329 /** 330 * 继续下载 331 * 332 * @param upgradeVersionDigest 版本摘要 333 * @param downloadNetwork 下载网络 334 * @return 调用结果 335 */ 336 resumeDownload(downloadVersionDigest: string, downloadNetwork: number): Promise<void> { 337 return new Promise((resolve, reject) => { 338 let versionDigestInfo: update.VersionDigestInfo = { 339 versionDigest: downloadVersionDigest 340 }; 341 342 let resumeDownloadOptions: update.ResumeDownloadOptions = { 343 allowNetwork: downloadNetwork 344 }; 345 this.otaUpdater?.resumeDownload(versionDigestInfo, resumeDownloadOptions).then(() => { 346 this.log('download succeeded.'); 347 resolve(); 348 }).catch((err: BusinessError) => { 349 this.logError('resumeDownload err:' + JSON.stringify(err)); 350 reject(err); 351 }); 352 }); 353 } 354 355 /** 356 * 取消升级 357 */ 358 cancel(): void { 359 (<any> this.otaUpdater).cancel(); 360 } 361 362 /** 363 * 取当前版本数据 364 * 365 * @return resolve 当前版本信息/reject 错误信息 366 */ 367 @enableTimeOutCheck() 368 async getCurrentVersionInfo(): Promise<UpgradeData<update.CurrentVersionInfo>> { 369 return new Promise((resolve, reject) => { 370 this.otaUpdater?.getCurrentVersionInfo().then((result: update.CurrentVersionInfo) => { 371 this.log('getCurrentVersionInfo result:' + JSON.stringify(result)); 372 let upgradeData: UpgradeData<update.CurrentVersionInfo> = { 373 callResult: UpgradeCallResult.OK, 374 data: result 375 }; 376 resolve(upgradeData); 377 }).catch((err: BusinessError) => { 378 this.logError('getCurrentVersionInfo err:' + JSON.stringify(err)); 379 let upgradeData: UpgradeData<update.CurrentVersionInfo> = { 380 callResult: UpgradeCallResult.ERROR, 381 error: err 382 }; 383 resolve(upgradeData); 384 }); 385 }); 386 } 387 388 private log(message: string): void { 389 LogUtils.log('UpdateManager', message); 390 } 391 392 private logError(message: string): void { 393 LogUtils.error('UpdateManager', message); 394 } 395} 396 397/** 398 * OtaStatus缓存/数据处理 399 * 400 * @since 2022-07-30 401 */ 402export class OtaStatusHolder { 403 private lastStatusHolder: StatusHolder; 404 405 /** 406 * 比较otaStatus与lastStatusHolder,并刷新lastStatusHolder 407 * 408 * @param otaStatus otaStatus 409 * @return otaStatus是否是重复事件 410 */ 411 isStatusChangedAndRefresh(otaStatus: OtaStatus, eventId?: update.EventId): boolean { 412 const STATUS_ALIVE_TIME = 1000; 413 const newStatus = this.makeStatusHolder(otaStatus, eventId); 414 let isChanged: boolean; 415 416 if (this.lastStatusHolder != null && 417 (newStatus.initTime - this.lastStatusHolder.initTime) < STATUS_ALIVE_TIME && 418 newStatus.status === this.lastStatusHolder.status) { 419 isChanged = false; 420 } else { 421 isChanged = true; 422 } 423 this.lastStatusHolder = newStatus; 424 return isChanged; 425 } 426 427 /** 428 * 序列化otaStatus,保存在StatusHolder 429 * 430 * @param otaStatus 431 * @param isCompareProgress 是否考虑进度标志位 432 */ 433 private makeStatusHolder(otaStatus: OtaStatus, eventId?: update.EventId): StatusHolder { 434 let otaStatusHolder: StatusHolder = { status: '', initTime: new Date().getTime() }; 435 if (otaStatus.status == null) { 436 otaStatusHolder.status = '_'; 437 } else { 438 otaStatusHolder.status = otaStatus.status + '_'; 439 } 440 let status: number = otaStatus.status; 441 let isCompareStatusProgress: boolean = this.isCompareStatusProgress(status); 442 if (otaStatus.percent == null || !isCompareStatusProgress) { 443 otaStatusHolder.status += '_'; 444 } else { 445 otaStatusHolder.status += otaStatus.percent + '_'; 446 } 447 otaStatusHolder.status += otaStatus.endReason; 448 otaStatusHolder.status += eventId; 449 450 return otaStatusHolder; 451 } 452 453 private isCompareStatusProgress(status: number): boolean { 454 return status === UpdateState.DOWNLOADING || status === UpdateState.INSTALLING; 455 } 456} 457 458/** 459 * 保存每次ota_status的信息 460 * 461 * @since 2022-07-18 462 */ 463export interface StatusHolder { 464 /** 465 * 序列化后的status 466 */ 467 status: string; 468 469 /** 470 * status接收的时间,ms 471 */ 472 initTime: number; 473} 474 475/** 476 * 信息 477 * 478 * @since 2022-10-25 479 */ 480export interface Message { 481 /** 482 * 上下文 483 */ 484 context: common.Context; 485 486 /** 487 * 事件 488 */ 489 eventInfo: update.EventInfo; 490} 491 492/** 493 * 通知的消息队列 494 * 495 * @since 2022-08-01 496 */ 497export class MessageQueue { 498 private queue: Array<Message>; 499 private handleMessage: (context: common.Context, eventInfo: update.EventInfo) => Promise<void>; 500 501 constructor(handleMessage: (context: common.Context, eventInfo: update.EventInfo) => Promise<void>) { 502 this.queue = new Array<Message>(); 503 this.handleMessage = handleMessage; 504 } 505 506 async execute(message: Message): Promise<void> { 507 if (!message) { 508 return; 509 } 510 this.offer(message); 511 if (this.queue.length === 1) { 512 await this.loop(); 513 } 514 } 515 516 isEmpty(): boolean { 517 return this.queue?.length === 0; 518 } 519 520 private async loop(): Promise<void> { 521 let message: Message = this.peek(); 522 if (message) { 523 await this.handleMessage?.(message.context, message.eventInfo).catch(err => { 524 LogUtils.error('MessageQueue', 'loop err:' + JSON.stringify(err)); 525 }); 526 this.poll(); 527 await this.loop(); 528 } 529 } 530 531 private offer(message: Message): void { 532 if (!message) { 533 return; 534 } 535 this.queue.push(message); 536 } 537 538 private poll(): void { 539 if (this.queue.length !== 0) { 540 this.queue.shift(); 541 } 542 } 543 544 private peek(): Message { 545 if (this.queue.length !== 0) { 546 return this.queue[0]; 547 } 548 return null; 549 } 550}