• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}