T-M2-01 存储/文件:KvStore(preferences 持久化 urlpath/upurlpath)、FileSystem(fileIo 建删读写/列目录/拷贝内置 rawfile)
T-M2-02 网络/下载:HttpClient(GET 禁缓存,契约 §4.3)、Downloader(http 流式写盘+进度,原生异步不阻塞 UI)
T-M2-03 解压:Unzipper(zlib.decompressFile,原生异步)。偏差:下载/解压用原生异步 API 即满足"UI 不阻塞",
独立 TaskScheduler(TaskPool) 推迟到 M5 真有 CPU 密集任务(MD5)时再加,避免投机性死代码。
T-M2-04 权限/上传:PermissionGuard(用时申请,沙箱文件免存储权限)、LocalUploadServer(骨架,本机端口服务 M3 落地)
devecocli build 通过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
60 lines
2.2 KiB
Plaintext
60 lines
2.2 KiB
Plaintext
/**
|
|
* 文件下载器(框架 §8.3 / §9)。封装 @kit.NetworkKit http 流式请求 requestInStream。
|
|
*
|
|
* 边收边写盘(不全量入内存,支持大 zip),经回调汇报百分比进度。
|
|
* http 流式请求为**原生异步、不阻塞 UI 线程**——故无需 TaskPool 即满足"下载不阻塞 UI"。
|
|
* 进度回调在 UI 线程触发(http 回调即在调用线程),调用方可直接更新 UI。
|
|
*/
|
|
import { http } from '@kit.NetworkKit';
|
|
import { fileIo as fs } from '@kit.CoreFileKit';
|
|
import { FileSystem } from '../fs/FileSystem';
|
|
import { Logger } from 'common';
|
|
|
|
/** 下载进度回调:percent 为 0~100 整数(总长未知时回 -1)。 */
|
|
export type ProgressCallback = (percent: number) => void;
|
|
|
|
export class Downloader {
|
|
private static readonly log: Logger = Logger.tag('Downloader');
|
|
|
|
/** 下载 url 到 savePath(覆盖旧文件),可选进度回调。失败抛出。 */
|
|
static async download(url: string, savePath: string, onProgress?: ProgressCallback): Promise<void> {
|
|
FileSystem.ensureDir(FileSystem.dirOf(savePath));
|
|
FileSystem.rmrf(savePath);
|
|
const file: fs.File = fs.openSync(savePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE | fs.OpenMode.TRUNC);
|
|
let offset: number = 0;
|
|
const req: http.HttpRequest = http.createHttp();
|
|
|
|
req.on('dataReceive', (chunk: ArrayBuffer) => {
|
|
fs.writeSync(file.fd, chunk, { offset });
|
|
offset += chunk.byteLength;
|
|
});
|
|
if (onProgress !== undefined) {
|
|
req.on('dataReceiveProgress', (info: http.DataReceiveProgressInfo) => {
|
|
const pct: number = info.totalSize > 0
|
|
? Math.floor(info.receiveSize * 100 / info.totalSize) : -1;
|
|
onProgress(pct);
|
|
});
|
|
}
|
|
|
|
try {
|
|
const code: number = await req.requestInStream(url, {
|
|
method: http.RequestMethod.GET,
|
|
usingCache: false,
|
|
connectTimeout: 15000,
|
|
readTimeout: 300000,
|
|
});
|
|
if (code < 200 || code >= 300) {
|
|
throw new Error(`download http ${code}`);
|
|
}
|
|
Downloader.log.i(`download ${url} -> ${savePath} (${offset} bytes)`);
|
|
} finally {
|
|
req.off('dataReceive');
|
|
if (onProgress !== undefined) {
|
|
req.off('dataReceiveProgress');
|
|
}
|
|
req.destroy();
|
|
fs.closeSync(file);
|
|
}
|
|
}
|
|
}
|