| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- import { http } from '@kit.NetworkKit';
- import { WebDavAccount } from '../../viewmodel/WebDavAccount';
- import Logger from '../util/Logger';
- import FileManager from '../util/FileManager';
- import { RemoteCacheType, resolveCacheFilePath, normalizeCacheRelativePath } from './RemoteSongCache';
- import { RemoteCacheManager, RemoteCacheRequest, RemoteCacheStrategy } from './RemoteCacheManager';
- import { fileIo } from '@kit.CoreFileKit';
- const TAG = 'heanup AudioStationFileCache';
- const CHUNK_SIZE = 4 * 1024 * 1024;
- interface AudioStationCacheOptions extends RemoteCacheRequest {
- account: WebDavAccount;
- songId: string;
- streamUrl: string;
- fileSize?: number;
- }
- class AudioStationCacheStrategy implements RemoteCacheStrategy {
- readonly type: RemoteCacheType = RemoteCacheType.AUDIOSTATION;
- async ensure(options: RemoteCacheRequest): Promise<string> {
- const audioOptions = options as AudioStationCacheOptions;
- return ensureAudioStationFileCachedInternal(
- audioOptions.account,
- audioOptions.songId,
- audioOptions.streamUrl,
- audioOptions.fileSize
- );
- }
- }
- async function ensureAudioStationFileCachedInternal(
- account: WebDavAccount,
- songId: string,
- streamUrl: string,
- fileSize?: number
- ): Promise<string> {
- const normalizedRelative = normalizeCacheRelativePath(songId);
- const pathInfo = await resolveCacheFilePath(
- RemoteCacheType.AUDIOSTATION,
- account.id?.toString(),
- normalizedRelative
- );
- const cachePath = pathInfo.cachePath;
- let exists = await FileManager.isExist(cachePath);
- if (exists) {
- const size = await FileManager.getFileSize(cachePath);
- if (size <= 0) {
- await FileManager.deleteFile(cachePath);
- exists = false;
- } else {
- Logger.info(TAG, `AudioStation 文件已缓存: ${cachePath}, size=${size}`);
- return cachePath;
- }
- }
- if (!exists) {
- try {
- Logger.info(TAG, `开始下载 AudioStation 文件: songId=${songId}, 到 ${cachePath}`);
- let finalFileSize = fileSize || 0;
- if (!finalFileSize || finalFileSize <= 0) {
- try {
- const headRequest = http.createHttp();
- const headResponse = await headRequest.request(streamUrl, {
- method: http.RequestMethod.HEAD,
- connectTimeout: 30000,
- readTimeout: 30000,
- expectDataType: http.HttpDataType.STRING
- });
- const contentLength = headResponse.header['Content-Length'] as string;
- if (contentLength) {
- finalFileSize = parseInt(contentLength, 10);
- }
- headRequest.destroy();
- } catch (error) {
- Logger.warn(TAG, `HEAD请求获取文件大小失败,将使用分片下载: ${(error as Error).message}`);
- }
- }
- await downloadAudioStationFileChunked(streamUrl, cachePath, finalFileSize);
- Logger.info(TAG, `AudioStation 文件下载完成: ${cachePath}`);
- } catch (error) {
- await FileManager.deleteFile(cachePath);
- const err = error as Error;
- Logger.error(TAG, `AudioStation 文件下载失败: ${err.message}`);
- throw err;
- }
- }
- return cachePath;
- }
- async function downloadAudioStationFileChunked(streamUrl: string, localPath: string, totalSize: number): Promise<void> {
- const file = fileIo.openSync(localPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.TRUNC);
- try {
- if (totalSize === 0) {
- await downloadSingleAudioStationChunk(streamUrl, 0, -1, file);
- } else {
- let downloadedSize: number = 0;
- while (downloadedSize < totalSize) {
- const rangeStart = downloadedSize;
- const rangeEnd = Math.min(downloadedSize + CHUNK_SIZE - 1, totalSize - 1);
- await downloadSingleAudioStationChunk(streamUrl, rangeStart, rangeEnd, file);
- downloadedSize = rangeEnd + 1;
- }
- }
- fileIo.closeSync(file);
- } finally {
- // file closed above
- }
- }
- async function downloadSingleAudioStationChunk(
- streamUrl: string,
- rangeStart: number,
- rangeEnd: number,
- file: fileIo.File
- ): Promise<void> {
- const httpRequest = http.createHttp();
- try {
- const options: http.HttpRequestOptions = {
- method: http.RequestMethod.GET,
- connectTimeout: 30000,
- readTimeout: 600000,
- expectDataType: http.HttpDataType.ARRAY_BUFFER
- };
- if (rangeEnd >= 0) {
- if (!options.header) {
- options.header = {};
- }
- options.header['Range'] = `bytes=${rangeStart}-${rangeEnd}`;
- }
- const response = await httpRequest.request(streamUrl, options);
- if (response.responseCode !== 200 && response.responseCode !== 206) {
- throw new Error(`AudioStation 下载失败 HTTP ${response.responseCode}`);
- }
- if (response.result instanceof ArrayBuffer) {
- const arrayBuffer = response.result as ArrayBuffer;
- if (!arrayBuffer || arrayBuffer.byteLength === 0) {
- throw new Error('AudioStation 下载的文件为空');
- }
- await fileIo.write(file.fd, arrayBuffer, {
- offset: rangeStart,
- length: arrayBuffer.byteLength
- });
- }
- } finally {
- httpRequest.destroy();
- }
- }
- RemoteCacheManager.registerStrategy(new AudioStationCacheStrategy());
- export async function ensureAudioStationFileCached(
- account: WebDavAccount,
- songId: string,
- streamUrl: string,
- fileSize?: number
- ): Promise<string> {
- return RemoteCacheManager.ensureCached(RemoteCacheType.AUDIOSTATION, {
- account,
- songId,
- streamUrl,
- fileSize
- } as AudioStationCacheOptions);
- }
|