react-native-update 10.37.5 → 10.37.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/harmony/pushy/BuildProfile.ets +3 -1
- package/harmony/pushy/src/main/ets/DownloadTask.ts +129 -52
- package/harmony/pushy/src/main/ets/{PushyFileJSBundleProvider.ets → PushyFileJSBundleProvider.ts} +11 -10
- package/harmony/pushy/src/main/ets/SaveFile.ts +34 -0
- package/harmony/pushy/src/main/ets/UpdateModuleImpl.ts +2 -2
- package/package.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Use these variables when you tailor your ArkTS code. They must be of the const type.
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
import packageJson from '../../package.json'
|
|
5
|
+
|
|
6
|
+
export const HAR_VERSION = packageJson.version;
|
|
5
7
|
export const BUILD_MODE_NAME = 'debug';
|
|
6
8
|
export const DEBUG = true;
|
|
7
9
|
export const TARGET_NAME = 'default';
|
|
@@ -5,6 +5,7 @@ import { zlib } from '@kit.BasicServicesKit';
|
|
|
5
5
|
import { EventHub } from './EventHub';
|
|
6
6
|
import { DownloadTaskParams } from './DownloadTaskParams';
|
|
7
7
|
import Pushy from 'librnupdate.so';
|
|
8
|
+
import { saveFileToSandbox } from './SaveFile';
|
|
8
9
|
|
|
9
10
|
interface ZipEntry {
|
|
10
11
|
filename: string;
|
|
@@ -18,7 +19,6 @@ interface ZipFile {
|
|
|
18
19
|
export class DownloadTask {
|
|
19
20
|
private context: common.Context;
|
|
20
21
|
private hash: string;
|
|
21
|
-
private readonly DOWNLOAD_CHUNK_SIZE = 4096;
|
|
22
22
|
private eventHub: EventHub;
|
|
23
23
|
|
|
24
24
|
constructor(context: common.Context) {
|
|
@@ -53,27 +53,104 @@ export class DownloadTask {
|
|
|
53
53
|
private async downloadFile(params: DownloadTaskParams): Promise<void> {
|
|
54
54
|
const httpRequest = http.createHttp();
|
|
55
55
|
this.hash = params.hash;
|
|
56
|
+
let writer: fileIo.File | null = null;
|
|
57
|
+
let contentLength = 0;
|
|
58
|
+
let received = 0;
|
|
59
|
+
let writeError: Error | null = null;
|
|
60
|
+
let writeQueue = Promise.resolve();
|
|
61
|
+
|
|
62
|
+
const closeWriter = async () => {
|
|
63
|
+
if (writer) {
|
|
64
|
+
await fileIo.close(writer);
|
|
65
|
+
writer = null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const dataEndPromise = new Promise<void>((resolve, reject) => {
|
|
70
|
+
httpRequest.on('dataEnd', () => {
|
|
71
|
+
writeQueue
|
|
72
|
+
.then(async () => {
|
|
73
|
+
if (writeError) {
|
|
74
|
+
throw writeError;
|
|
75
|
+
}
|
|
76
|
+
await closeWriter();
|
|
77
|
+
resolve();
|
|
78
|
+
})
|
|
79
|
+
.catch(async error => {
|
|
80
|
+
await closeWriter();
|
|
81
|
+
reject(error);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
56
85
|
|
|
57
86
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
await fileIo.mkdir(targetDir);
|
|
70
|
-
}
|
|
87
|
+
let exists = fileIo.accessSync(params.targetFile);
|
|
88
|
+
if (exists) {
|
|
89
|
+
await fileIo.unlink(params.targetFile);
|
|
90
|
+
} else {
|
|
91
|
+
const targetDir = params.targetFile.substring(
|
|
92
|
+
0,
|
|
93
|
+
params.targetFile.lastIndexOf('/'),
|
|
94
|
+
);
|
|
95
|
+
exists = fileIo.accessSync(targetDir);
|
|
96
|
+
if (!exists) {
|
|
97
|
+
await fileIo.mkdir(targetDir);
|
|
71
98
|
}
|
|
72
|
-
} catch (error) {
|
|
73
|
-
throw error;
|
|
74
99
|
}
|
|
75
100
|
|
|
76
|
-
|
|
101
|
+
writer = await fileIo.open(
|
|
102
|
+
params.targetFile,
|
|
103
|
+
fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
httpRequest.on('headersReceive', (header: Record<string, string>) => {
|
|
107
|
+
if (!header) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const lengthKey = Object.keys(header).find(
|
|
111
|
+
key => key.toLowerCase() === 'content-length',
|
|
112
|
+
);
|
|
113
|
+
if (!lengthKey) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const length = parseInt(header[lengthKey], 10);
|
|
117
|
+
if (!Number.isNaN(length)) {
|
|
118
|
+
contentLength = length;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
httpRequest.on('dataReceive', (data: ArrayBuffer) => {
|
|
123
|
+
if (writeError) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
received += data.byteLength;
|
|
127
|
+
writeQueue = writeQueue.then(async () => {
|
|
128
|
+
if (!writer || writeError) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
await fileIo.write(writer.fd, data);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
writeError = error as Error;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.onProgressUpdate(received, contentLength);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
httpRequest.on(
|
|
141
|
+
'dataReceiveProgress',
|
|
142
|
+
(data: http.DataReceiveProgressInfo) => {
|
|
143
|
+
if (data.totalSize > 0) {
|
|
144
|
+
contentLength = data.totalSize;
|
|
145
|
+
}
|
|
146
|
+
if (data.receiveSize > received) {
|
|
147
|
+
received = data.receiveSize;
|
|
148
|
+
}
|
|
149
|
+
this.onProgressUpdate(received, contentLength);
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const responseCode = await httpRequest.requestInStream(params.url, {
|
|
77
154
|
method: http.RequestMethod.GET,
|
|
78
155
|
readTimeout: 60000,
|
|
79
156
|
connectTimeout: 60000,
|
|
@@ -81,32 +158,14 @@ export class DownloadTask {
|
|
|
81
158
|
'Content-Type': 'application/octet-stream',
|
|
82
159
|
},
|
|
83
160
|
});
|
|
84
|
-
if (
|
|
85
|
-
throw Error(`Server error: ${
|
|
161
|
+
if (responseCode > 299) {
|
|
162
|
+
throw Error(`Server error: ${responseCode}`);
|
|
86
163
|
}
|
|
87
164
|
|
|
88
|
-
|
|
89
|
-
const writer = await fileIo.open(
|
|
90
|
-
params.targetFile,
|
|
91
|
-
fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE,
|
|
92
|
-
);
|
|
93
|
-
let received = 0;
|
|
94
|
-
const data = response.result as ArrayBuffer;
|
|
95
|
-
const chunks = Math.ceil(data.byteLength / this.DOWNLOAD_CHUNK_SIZE);
|
|
96
|
-
for (let i = 0; i < chunks; i++) {
|
|
97
|
-
const start = i * this.DOWNLOAD_CHUNK_SIZE;
|
|
98
|
-
const end = Math.min(start + this.DOWNLOAD_CHUNK_SIZE, data.byteLength);
|
|
99
|
-
const chunk = data.slice(start, end);
|
|
100
|
-
|
|
101
|
-
await fileIo.write(writer.fd, chunk);
|
|
102
|
-
received += chunk.byteLength;
|
|
103
|
-
|
|
104
|
-
this.onProgressUpdate(received, contentLength);
|
|
105
|
-
}
|
|
106
|
-
await fileIo.close(writer);
|
|
165
|
+
await dataEndPromise;
|
|
107
166
|
const stats = await fileIo.stat(params.targetFile);
|
|
108
167
|
const fileSize = stats.size;
|
|
109
|
-
if (fileSize !== contentLength) {
|
|
168
|
+
if (contentLength > 0 && fileSize !== contentLength) {
|
|
110
169
|
throw Error(
|
|
111
170
|
`Download incomplete: expected ${contentLength} bytes but got ${stats.size} bytes`,
|
|
112
171
|
);
|
|
@@ -115,6 +174,15 @@ export class DownloadTask {
|
|
|
115
174
|
console.error('Download failed:', error);
|
|
116
175
|
throw error;
|
|
117
176
|
} finally {
|
|
177
|
+
try {
|
|
178
|
+
await closeWriter();
|
|
179
|
+
} catch (closeError) {
|
|
180
|
+
console.error('Failed to close file:', closeError);
|
|
181
|
+
}
|
|
182
|
+
httpRequest.off('headersReceive');
|
|
183
|
+
httpRequest.off('dataReceive');
|
|
184
|
+
httpRequest.off('dataReceiveProgress');
|
|
185
|
+
httpRequest.off('dataEnd');
|
|
118
186
|
httpRequest.destroy();
|
|
119
187
|
}
|
|
120
188
|
}
|
|
@@ -166,7 +234,8 @@ export class DownloadTask {
|
|
|
166
234
|
|
|
167
235
|
return { entries };
|
|
168
236
|
} catch (error) {
|
|
169
|
-
|
|
237
|
+
error.message = 'Failed to process unzipped files:' + error.message;
|
|
238
|
+
console.error(error);
|
|
170
239
|
throw error;
|
|
171
240
|
}
|
|
172
241
|
}
|
|
@@ -195,10 +264,6 @@ export class DownloadTask {
|
|
|
195
264
|
|
|
196
265
|
const copies = obj.copies as Record<string, string>;
|
|
197
266
|
for (const [to, rawPath] of Object.entries(copies)) {
|
|
198
|
-
if (!rawPath.startsWith('resources/rawfile/')) {
|
|
199
|
-
// skip other resource
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
267
|
let from = rawPath.replace('resources/rawfile/', '');
|
|
203
268
|
if (from === '') {
|
|
204
269
|
from = to;
|
|
@@ -244,7 +309,7 @@ export class DownloadTask {
|
|
|
244
309
|
await fileIo.close(writer);
|
|
245
310
|
continue;
|
|
246
311
|
} catch (error) {
|
|
247
|
-
|
|
312
|
+
error.message = 'Failed to process bundle patch:' + error.message;
|
|
248
313
|
throw error;
|
|
249
314
|
}
|
|
250
315
|
}
|
|
@@ -365,15 +430,27 @@ export class DownloadTask {
|
|
|
365
430
|
|
|
366
431
|
for (const [from, targets] of copyList.entries()) {
|
|
367
432
|
currentFrom = from;
|
|
368
|
-
|
|
433
|
+
if (from.startsWith('resources/base/media/')) {
|
|
434
|
+
const mediaName = from
|
|
435
|
+
.replace('resources/base/media/', '')
|
|
436
|
+
.split('.')[0];
|
|
437
|
+
const mediaBuffer = await resourceManager.getMediaByName(mediaName);
|
|
438
|
+
for (const target of targets) {
|
|
439
|
+
const fileStream = fileIo.createStreamSync(target, 'w+');
|
|
440
|
+
fileStream.writeSync(mediaBuffer);
|
|
441
|
+
fileStream.close();
|
|
442
|
+
}
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const fromContent = await resourceManager.getRawFd(from);
|
|
369
446
|
for (const target of targets) {
|
|
370
|
-
|
|
371
|
-
fileStream.writeSync(fromContent.buffer);
|
|
372
|
-
fileStream.close();
|
|
447
|
+
saveFileToSandbox(fromContent, target);
|
|
373
448
|
}
|
|
374
449
|
}
|
|
375
450
|
} catch (error) {
|
|
376
|
-
|
|
451
|
+
error.message =
|
|
452
|
+
'Copy from resource failed:' + currentFrom + ',' + error.message;
|
|
453
|
+
console.error(error);
|
|
377
454
|
throw error;
|
|
378
455
|
}
|
|
379
456
|
}
|
|
@@ -406,7 +483,8 @@ export class DownloadTask {
|
|
|
406
483
|
}
|
|
407
484
|
}
|
|
408
485
|
} catch (error) {
|
|
409
|
-
|
|
486
|
+
error.message = 'Cleanup failed:' + error.message;
|
|
487
|
+
console.error(error);
|
|
410
488
|
throw error;
|
|
411
489
|
}
|
|
412
490
|
}
|
|
@@ -432,7 +510,6 @@ export class DownloadTask {
|
|
|
432
510
|
default:
|
|
433
511
|
throw Error(`Unknown task type: ${params.type}`);
|
|
434
512
|
}
|
|
435
|
-
|
|
436
513
|
} catch (error) {
|
|
437
514
|
console.error('Task execution failed:', error.message);
|
|
438
515
|
if (params.type !== DownloadTaskParams.TASK_TYPE_CLEANUP) {
|
package/harmony/pushy/src/main/ets/{PushyFileJSBundleProvider.ets → PushyFileJSBundleProvider.ts}
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FileJSBundle,
|
|
3
|
-
HotReloadConfig,
|
|
4
3
|
JSBundleProvider,
|
|
5
|
-
JSBundleProviderError
|
|
4
|
+
JSBundleProviderError,
|
|
6
5
|
} from '@rnoh/react-native-openharmony';
|
|
7
6
|
import common from '@ohos.app.ability.common';
|
|
8
7
|
import fs from '@ohos.file.fs';
|
|
@@ -10,7 +9,7 @@ import { UpdateContext } from './UpdateContext';
|
|
|
10
9
|
|
|
11
10
|
export class PushyFileJSBundleProvider extends JSBundleProvider {
|
|
12
11
|
private updateContext: UpdateContext;
|
|
13
|
-
private path: string = ''
|
|
12
|
+
private path: string = '';
|
|
14
13
|
|
|
15
14
|
constructor(context: common.UIAbilityContext) {
|
|
16
15
|
super();
|
|
@@ -26,24 +25,26 @@ export class PushyFileJSBundleProvider extends JSBundleProvider {
|
|
|
26
25
|
if (!this.path) {
|
|
27
26
|
throw new JSBundleProviderError({
|
|
28
27
|
whatHappened: 'No pushy bundle found. using default bundle',
|
|
29
|
-
howCanItBeFixed: ['']
|
|
30
|
-
})
|
|
28
|
+
howCanItBeFixed: [''],
|
|
29
|
+
});
|
|
31
30
|
}
|
|
32
31
|
try {
|
|
33
32
|
await fs.access(this.path, fs.OpenMode.READ_ONLY);
|
|
34
33
|
return {
|
|
35
|
-
filePath: this.path
|
|
36
|
-
}
|
|
34
|
+
filePath: this.path,
|
|
35
|
+
};
|
|
37
36
|
} catch (error) {
|
|
38
37
|
throw new JSBundleProviderError({
|
|
39
38
|
whatHappened: `Couldn't load JSBundle from ${this.path}`,
|
|
40
39
|
extraData: error,
|
|
41
|
-
howCanItBeFixed: [
|
|
42
|
-
|
|
40
|
+
howCanItBeFixed: [
|
|
41
|
+
`Check if a bundle exists at "${this.path}" on your device.`,
|
|
42
|
+
],
|
|
43
|
+
});
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
getAppKeys(): string[] {
|
|
47
48
|
return [];
|
|
48
49
|
}
|
|
49
|
-
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import resourceManager from '@ohos.resourceManager';
|
|
2
|
+
import fs, { ReadOptions } from '@ohos.file.fs';
|
|
3
|
+
|
|
4
|
+
export const saveFileToSandbox = (
|
|
5
|
+
from: resourceManager.RawFileDescriptor,
|
|
6
|
+
toPath: string,
|
|
7
|
+
) => {
|
|
8
|
+
let to = fs.openSync(toPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
|
|
9
|
+
|
|
10
|
+
let bufferSize = 30000;
|
|
11
|
+
let buffer = new ArrayBuffer(bufferSize); // 创建buffer缓冲区
|
|
12
|
+
// 要copy的文件的offset和length
|
|
13
|
+
let currentOffset = from.offset;
|
|
14
|
+
let readOption: ReadOptions = {
|
|
15
|
+
offset: currentOffset, // 期望读取文件的位置。可选,默认从当前位置开始读
|
|
16
|
+
length: bufferSize, // 每次期望读取数据的长度。可选,默认缓冲区长度
|
|
17
|
+
};
|
|
18
|
+
// 后面len会一直减,直到没有
|
|
19
|
+
while (true) {
|
|
20
|
+
// 读取buffer容量的内容
|
|
21
|
+
let readLength = fs.readSync(from.fd, buffer, readOption);
|
|
22
|
+
// 写入buffer容量的内容
|
|
23
|
+
fs.writeSync(to.fd, buffer, { length: readLength });
|
|
24
|
+
// 判断后续内容,修改读文件的参数
|
|
25
|
+
// buffer没读满代表文件读完了
|
|
26
|
+
if (readLength < bufferSize) {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
if (readOption.offset) {
|
|
30
|
+
readOption.offset += readLength;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
fs.close(to);
|
|
34
|
+
};
|
|
@@ -69,7 +69,7 @@ export class UpdateModuleImpl {
|
|
|
69
69
|
): Promise<boolean> {
|
|
70
70
|
const hash = options.hash;
|
|
71
71
|
if (!hash) {
|
|
72
|
-
throw Error('hash
|
|
72
|
+
throw Error('empty hash');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
try {
|
|
@@ -87,7 +87,7 @@ export class UpdateModuleImpl {
|
|
|
87
87
|
return true;
|
|
88
88
|
} catch (error) {
|
|
89
89
|
logger.error(TAG, `markSuccess failed: ${error}`);
|
|
90
|
-
throw
|
|
90
|
+
throw error;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|