1
0
mirror of https://gitcode.com/gh_mirrors/re/react-native-pushy.git synced 2025-10-07 22:55:13 +08:00
Code Issues Packages Projects Releases Wiki Activity GitHub Gitee

react-native-pushy适配harmony (#461)

* init

* update

* udpate

* update

* update

* update

* add pushy build time logic
This commit is contained in:
波仔糕
2024-12-26 12:11:52 +08:00
committed by GitHub
parent 1ad0926a24
commit 822602add9
151 changed files with 25412 additions and 3 deletions

View File

@@ -0,0 +1,492 @@
import http from '@ohos.net.http';
import fileIo from '@ohos.file.fs';
import util from '@ohos.util';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@kit.BasicServicesKit';
import { buffer } from '@kit.ArkTS';
import zip from '@ohos.zlib';
import { EventHub } from './EventHub';
import { DownloadTaskParams } from './DownloadTaskParams';
import Pushy from 'librnupdate.so';
interface ZipEntry {
filename: string;
content: ArrayBuffer;
}
interface ZipFile {
entries: ZipEntry[];
}
export class DownloadTask {
private context: common.Context;
private hash: string;
private readonly DOWNLOAD_CHUNK_SIZE = 4096;
private eventHub: EventHub;
constructor(context: common.Context) {
this.context = context;
this.eventHub = EventHub.getInstance();
}
private async removeDirectory(path: string): Promise<void> {
try {
const res = fileIo.accessSync(path);
if (res) {
const stat = await fileIo.stat(path);
if (stat.isDirectory()) {
const files = await fileIo.listFile(path);
for (const file of files) {
if (file === '.' || file === '..') continue;
await this.removeDirectory(`${path}/${file}`);
}
await fileIo.rmdir(path);
} else {
await fileIo.unlink(path);
}
}
} catch (error) {
console.error('Failed to delete directory:', error);
throw error;
}
}
private async downloadFile(params: DownloadTaskParams): Promise<void> {
const httpRequest = http.createHttp();
this.hash = params.hash;
try {
try {
const exists = fileIo.accessSync(params.targetFile);
if (exists) {
await fileIo.unlink(params.targetFile);
}else{
const targetDir = params.targetFile.substring(
0,
params.targetFile.lastIndexOf('/'),
);
await fileIo.mkdir(targetDir);
}
} catch (error) {
}
const response = await httpRequest.request(params.url, {
method: http.RequestMethod.GET,
readTimeout: 60000,
connectTimeout: 60000,
header: {
'Content-Type': 'application/octet-stream',
},
});
if (response.responseCode > 299) {
throw new Error(`Server error: ${response.responseCode}`);
}
const contentLength = parseInt(response.header['Content-Length'] || '0');
const writer = await fileIo.open(
params.targetFile,
fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE,
);
let received = 0;
const data = response.result as ArrayBuffer;
const chunks = Math.ceil(data.byteLength / this.DOWNLOAD_CHUNK_SIZE);
for (let i = 0; i < chunks; i++) {
const start = i * this.DOWNLOAD_CHUNK_SIZE;
const end = Math.min(start + this.DOWNLOAD_CHUNK_SIZE, data.byteLength);
const chunk = data.slice(start, end);
await fileIo.write(writer.fd, chunk);
received += chunk.byteLength;
this.onProgressUpdate(received, contentLength);
}
await fileIo.close(writer);
const stat = await fileIo.stat(params.targetFile);
const fileSize = stat.size;
} catch (error) {
console.error('Download failed:', error);
throw error;
} finally {
httpRequest.destroy();
}
}
private onProgressUpdate(received: number, total: number): void {
this.eventHub.emit('downloadProgress', {
received,
total,
hash: this.hash,
});
}
private async copyFile(from: string, to: string): Promise<void> {
let reader;
let writer;
try {
reader = fileIo.openSync(from, fileIo.OpenMode.READ_ONLY);
writer = fileIo.openSync(
to,
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
);
const arrayBuffer = new ArrayBuffer(4096);
let bytesRead: number;
do {
bytesRead = await fileIo
.read(reader.fd, arrayBuffer)
.catch((err: BusinessError) => {
throw new Error(
`Error reading file: ${err.message}, code: ${err.code}`,
);
});
if (bytesRead > 0) {
const buf = buffer.from(arrayBuffer, 0, bytesRead);
await fileIo
.write(writer.fd, buf.buffer, {
offset: 0,
length: bytesRead,
})
.catch((err: BusinessError) => {
throw new Error(
`Error writing file: ${err.message}, code: ${err.code}`,
);
});
}
} while (bytesRead > 0);
console.info('File copied successfully');
} catch (error) {
console.error('Copy file failed:', error);
throw error;
} finally {
if (reader !== undefined) {
fileIo.closeSync(reader);
}
if (writer !== undefined) {
fileIo.closeSync(writer);
}
}
}
private async doFullPatch(params: DownloadTaskParams): Promise<void> {
await this.downloadFile(params);
await this.removeDirectory(params.unzipDirectory);
await fileIo.mkdir(params.unzipDirectory);
try {
await zip.decompressFile(params.targetFile, params.unzipDirectory);
} catch (error) {
console.error('Unzip failed:', error);
throw error;
}
}
private async processUnzippedFiles(directory: string): Promise<ZipFile> {
const entries: ZipEntry[] = [];
try {
const files = await fileIo.listFile(directory);
for (const file of files) {
if (file === '.' || file === '..') continue;
const filePath = `${directory}/${file}`;
const stat = await fileIo.stat(filePath);
if (!stat.isDirectory()) {
const reader = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
const fileSize = stat.size;
const content = new ArrayBuffer(fileSize);
try {
await fileIo.read(reader.fd, content);
entries.push({
filename: file,
content: content,
});
} finally {
await fileIo.close(reader);
}
}
}
return { entries };
} catch (error) {
console.error('Failed to process unzipped files:', error);
throw error;
}
}
private async doPatchFromApp(params: DownloadTaskParams): Promise<void> {
await this.downloadFile(params);
await this.removeDirectory(params.unzipDirectory);
await fileIo.mkdir(params.unzipDirectory);
let foundDiff = false;
let foundBundlePatch = false;
const copyList: Map<string, Array<any>> = new Map();
await zip.decompressFile(params.targetFile, params.unzipDirectory);
const zipFile = await this.processUnzippedFiles(params.unzipDirectory);
for (const entry of zipFile.entries) {
const fn = entry.filename;
if (fn === '__diff.json') {
foundDiff = true;
let jsonContent = '';
const bufferArray = new Uint8Array(entry.content);
for (let i = 0; i < bufferArray.length; i++) {
jsonContent += String.fromCharCode(bufferArray[i]);
}
const obj = JSON.parse(jsonContent);
const copies = obj.copies;
for (const to in copies) {
let from = copies[to];
if (from === '') {
from = to;
}
if (!copyList.has(from)) {
copyList.set(from, []);
}
const target = copyList.get(from);
if (target) {
const toFile = `${params.unzipDirectory}/${to}`;
target.push(toFile);
}
}
continue;
}
if (fn === 'bundle.harmony.js.patch') {
foundBundlePatch = true;
try {
const resourceManager = this.context.resourceManager;
const originContent = await resourceManager.getRawFileContent(
'bundle.harmony.js',
);
const patched = await Pushy.hdiffPatch(
new Uint8Array(originContent.buffer),
new Uint8Array(entry.content),
);
const outputFile = `${params.unzipDirectory}/bundle.harmony.js`;
const writer = await fileIo.open(
outputFile,
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
);
const chunkSize = 4096;
let bytesWritten = 0;
const totalLength = patched.byteLength;
while (bytesWritten < totalLength) {
const chunk = patched.slice(bytesWritten, bytesWritten + chunkSize);
await fileIo.write(writer.fd, chunk);
bytesWritten += chunk.byteLength;
}
await fileIo.close(writer);
continue;
} catch (error) {
console.error('Failed to process bundle patch:', error);
throw error;
}
}
await zip.decompressFile(entry.filename, params.unzipDirectory);
}
if (!foundDiff) {
throw new Error('diff.json not found');
}
if (!foundBundlePatch) {
throw new Error('bundle patch not found');
}
await this.copyFromResource(copyList);
}
private async doPatchFromPpk(params: DownloadTaskParams): Promise<void> {
await this.downloadFile(params);
await this.removeDirectory(params.unzipDirectory);
await fileIo.mkdir(params.unzipDirectory);
let foundDiff = false;
let foundBundlePatch = false;
const copyList: Map<string, Array<any>> = new Map();
await zip.decompressFile(params.targetFile, params.unzipDirectory);
const zipFile = await this.processUnzippedFiles(params.unzipDirectory);
for (const entry of zipFile.entries) {
const fn = entry.filename;
if (fn === '__diff.json') {
foundDiff = true;
let jsonContent = '';
const bufferArray = new Uint8Array(entry.content);
for (let i = 0; i < bufferArray.length; i++) {
jsonContent += String.fromCharCode(bufferArray[i]);
}
const obj = JSON.parse(jsonContent);
const copies = obj.copies;
for (const to in copies) {
let from = copies[to];
if (from === '') {
from = to;
}
if (!copyList.has(from)) {
copyList.set(from, []);
}
const target = copyList.get(from);
if (target) {
const toFile = `${params.unzipDirectory}/${to}`;
target.push(toFile);
}
}
continue;
}
if (fn === 'bundle.harmony.js.patch') {
foundBundlePatch = true;
const filePath = params.originDirectory + '/bundle.harmony.js';
const res = fileIo.accessSync(filePath);
if (res) {
const stat = await fileIo.stat(filePath);
const reader = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
const fileSize = stat.size;
const originContent = new ArrayBuffer(fileSize);
try {
await fileIo.read(reader.fd, originContent);
const patched = await Pushy.hdiffPatch(
new Uint8Array(originContent),
new Uint8Array(entry.content),
);
const outputFile = `${params.unzipDirectory}/bundle.harmony.js`;
const writer = await fileIo.open(outputFile, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
const chunkSize = 4096;
let bytesWritten = 0;
const totalLength = patched.byteLength;
while (bytesWritten < totalLength) {
const chunk = patched.slice(bytesWritten, bytesWritten + chunkSize);
await fileIo.write(writer.fd, chunk);
bytesWritten += chunk.byteLength;
}
await fileIo.close(writer);
continue;
} finally {
await fileIo.close(reader);
}
}
}
await zip.decompressFile(entry.filename, params.unzipDirectory);
}
if (!foundDiff) {
throw new Error('diff.json not found');
}
if (!foundBundlePatch) {
throw new Error('bundle patch not found');
}
console.info('Patch from PPK completed');
}
private async copyFromResource(
copyList: Map<string, Array<string>>,
): Promise<void> {
try {
const bundlePath = this.context.bundleCodeDir;
const files = await fileIo.listFile(bundlePath);
for (const file of files) {
if (file === '.' || file === '..') continue;
const targets = copyList.get(file);
if (targets) {
let lastTarget: string | undefined;
for (const target of targets) {
console.info(`Copying from resource ${file} to ${target}`);
if (lastTarget) {
await this.copyFile(lastTarget, target);
} else {
const sourcePath = `${bundlePath}/${file}`;
await this.copyFile(sourcePath, target);
lastTarget = target;
}
}
}
}
} catch (error) {
console.error('Copy from resource failed:', error);
throw error;
}
}
private async doCleanUp(params: DownloadTaskParams): Promise<void> {
const DAYS_TO_KEEP = 7;
const now = Date.now();
const maxAge = DAYS_TO_KEEP * 24 * 60 * 60 * 1000;
try {
const files = await fileIo.listFile(params.unzipDirectory);
for (const file of files) {
if (file.startsWith('.')) continue;
const filePath = `${params.unzipDirectory}/${file}`;
const stat = await fileIo.stat(filePath);
if (
now - stat.mtime > maxAge &&
file !== params.hash &&
file !== params.originHash
) {
if (stat.isDirectory()) {
await this.removeDirectory(filePath);
} else {
await fileIo.unlink(filePath);
}
}
}
} catch (error) {
console.error('Cleanup failed:', error);
throw error;
}
}
public async execute(params: DownloadTaskParams): Promise<void> {
try {
switch (params.type) {
case DownloadTaskParams.TASK_TYPE_PATCH_FULL:
await this.doFullPatch(params);
break;
case DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP:
await this.doPatchFromApp(params);
break;
case DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK:
await this.doPatchFromPpk(params);
break;
case DownloadTaskParams.TASK_TYPE_CLEANUP:
await this.doCleanUp(params);
break;
case DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD:
await this.downloadFile(params);
break;
default:
throw new Error(`Unknown task type: ${params.type}`);
}
params.listener?.onDownloadCompleted(params);
} catch (error) {
console.error('Task execution failed:', error);
if (params.type !== DownloadTaskParams.TASK_TYPE_CLEANUP) {
try {
if (params.type === DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD) {
await fileIo.unlink(params.targetFile);
} else {
await this.removeDirectory(params.unzipDirectory);
}
} catch (cleanupError) {
console.error('Cleanup after error failed:', cleanupError);
}
}
params.listener?.onDownloadFailed(error);
}
}
}