1/*
2 * Copyright (C) 2024 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 BackupExtensionAbility, {BundleVersion} from '@ohos.application.BackupExtensionAbility';
17import fs from '@ohos.file.fs';
18// @ts-ignore
19import ringtonerestore from '@ohos.multimedia.ringtonerestore';
20
21const TAG = 'RingtoneBackupExtAbility';
22
23const ringtonePath = '/storage/media/local/files/Ringtone/';
24
25const RESTORE_SCENE_TYPE_DUAL_UPGRADE : number = 0;
26const RESTORE_SCENE_TYPE_SINGLE_CLONE : number = 1;
27const RESTORE_SCENE_TYPE_DUAL_CLONE : number = 2;
28
29const UPGRADE_NAME = '0.0.0.0';
30const DUAL_FRAME_CLONE_NAME = '99.99.99.999';
31
32const ON_RESTORE_COMMON_CODE : string = '0';
33const ON_RESTORE_ERROR_CODE : string = '13500099';
34
35export interface IResultExInfo {
36  resultInfo: [IResultErrorInfo];
37}
38
39export interface IResultErrorInfo {
40  type: string;
41  errorCode: string;
42  errorInfo: string;
43}
44
45export default class RingtoneBackupExtAbility extends BackupExtensionAbility {
46  async onBackup() : Promise<void> {
47    console.log(TAG, 'onBackup ok.');
48  }
49
50  async onRestoreEx(bundleVersion: BundleVersion, restoreInfo: string): Promise<string> {
51    console.log(TAG, `onRestoreEx ok ${JSON.stringify(bundleVersion)}`);
52    console.time(TAG + ' RESTORE');
53    const backupBasePath = this.context.backupDir + 'restore';
54    const backupFilePath = backupBasePath + '/storage/media/local/files/Ringtone/';
55    let srcPath:string = backupFilePath;
56    let destPath:string = ringtonePath;
57    let cloneType:number;
58    if (bundleVersion.name.startsWith(UPGRADE_NAME)) {
59      cloneType = RESTORE_SCENE_TYPE_DUAL_UPGRADE;
60    } else if (bundleVersion.name === DUAL_FRAME_CLONE_NAME && bundleVersion.code === 0) {
61      cloneType = RESTORE_SCENE_TYPE_DUAL_CLONE;
62    } else {
63      cloneType = RESTORE_SCENE_TYPE_SINGLE_CLONE;
64    }
65    let restoreResult:number = await ringtonerestore.startRestore(cloneType, backupBasePath);
66    console.log(TAG, `restoreResult:${restoreResult}`);
67    console.timeEnd(TAG + ' RESTORE');
68    console.time(TAG + ' MOVE REST FILES');
69    await this.moveRestFiles(srcPath, destPath);
70    console.timeEnd(TAG + ' MOVE REST FILES');
71    let result:boolean = restoreResult === 0;
72    let resultExInfo: IResultExInfo = {
73      resultInfo: [
74        {
75          type: 'ErrorInfo',
76          errorCode: result ? ON_RESTORE_COMMON_CODE : ON_RESTORE_ERROR_CODE, // 如果成功,错误码返回0,如果失败,返回约定的错误码
77          errorInfo: '',
78        }
79      ]
80    };
81    let resultInfo: string = JSON.stringify(resultExInfo);
82    console.log(TAG, `restore end resultInfo:${resultInfo}`);
83    return resultInfo;
84  }
85
86  private isFileExist(filePath : string) : boolean {
87    try {
88      return fs.accessSync(filePath);
89    } catch (err) {
90      console.error(TAG, `accessSync failed, message = ${err.message}; code = ${err.code}`);
91      return false;
92    }
93  }
94
95  private async moveRestFiles(srcPath : string, destPath : string) : Promise<void> {
96    console.log(TAG, 'Start to move rest files.');
97    const MOVE_ERR_CODE = 13900015;
98    let conflictList = [];
99    try {
100      console.log(TAG, `move dir from [${srcPath}] to [${destPath}]`);
101      await fs.copyDir(srcPath, destPath, 0);
102    } catch (err) {
103      console.log(TAG, `catch err: ${JSON.stringify(err)}`);
104      if (err.code === MOVE_ERR_CODE) {
105        conflictList = err.data;
106      } else {
107        console.error(TAG, `move directory failed, message = ${err.message}, code = ${err.code}`);
108      }
109    }
110    for (let i = 0; i < conflictList.length; i++) {
111      console.log(TAG, `move conflect file from [${conflictList[i].srcFile}] to [${conflictList[i].destFile}]`);
112      try {
113        await this.moveConflictFile(conflictList[i].srcFile, conflictList[i].destFile);
114      } catch (err) {
115        console.error(TAG, `MoveConflictFile failed, message = ${err.message}, code = ${err.code}`);
116      }
117    }
118  }
119
120  private async moveConflictFile(srcFile : string, dstFile : string) : Promise<void> {
121    let srcStat = fs.statSync(srcFile);
122    let dstStat = fs.statSync(dstFile);
123    if (srcStat.size === dstStat.size) {
124      console.log(TAG, `conflictFile is same, srcFile: [${srcFile}] size: (${srcStat.size}), dstFile: [${dstFile}] size: (${dstStat.size}), return.`);
125      return;
126    }
127    const srcArr = srcFile.split('/');
128    const dstArr = dstFile.split('/');
129    const srcFileName = srcArr[srcArr.length - 1];
130    const dirPath = dstArr.splice(0, dstArr.length - 1).join('/');
131    let fileExt : string = '';
132    let fileNameWithoutExt = srcFileName;
133    if (srcFileName.lastIndexOf('.') !== -1) {
134      let tmpValue = srcFileName.split('.').pop();
135      if (tmpValue !== undefined) {
136        fileExt = tmpValue;
137        fileNameWithoutExt = srcFileName.slice(0, srcFileName.length - fileExt.length - 1);
138      }
139    }
140    let newFileName = srcFileName;
141    let count = 1;
142    while (this.isFileExist(`${dirPath}/${newFileName}`)) {
143      if (fileExt === '') {
144        newFileName = `${fileNameWithoutExt}(${count})`;
145      } else {
146        newFileName = `${fileNameWithoutExt}(${count}).${fileExt}`;
147      }
148      count++;
149    }
150    try {
151      await fs.copyFile(srcFile, `${dirPath}/${newFileName}`);
152    } catch (err) {
153      console.error(TAG, `moveFile file failed, message = ${err.message}; code = ${err.code}`);
154    }
155  }
156}