refactor: unified path resolver on all platforms (#1730)

This commit is contained in:
Huang Xin
2025-08-01 22:51:17 +08:00
committed by GitHub
parent 7e559c5b90
commit 288abc678c
10 changed files with 87 additions and 68 deletions
@@ -14,17 +14,25 @@ import {
} from '@tauri-apps/plugin-fs';
import { convertFileSrc } from '@tauri-apps/api/core';
import { open as openDialog } from '@tauri-apps/plugin-dialog';
import { join, basename, appDataDir, appCacheDir } from '@tauri-apps/api/path';
import {
join,
basename,
appDataDir,
appConfigDir,
appCacheDir,
appLogDir,
tempDir,
} from '@tauri-apps/api/path';
import { type as osType } from '@tauri-apps/plugin-os';
import { Book } from '@/types/book';
import { FileSystem, BaseDir, AppPlatform } from '@/types/system';
import { getOSPlatform, isContentURI, isValidURL } from '@/utils/misc';
import { getOSPlatform, isContentURI, isFileURI, isValidURL } from '@/utils/misc';
import { getCoverFilename, getFilename } from '@/utils/book';
import { copyURIToPath } from '@/utils/bridge';
import { NativeFile, RemoteFile } from '@/utils/file';
import { BaseAppService } from './appService';
import { BaseAppService, ResolvedPath } from './appService';
import { LOCAL_BOOKS_SUBDIR } from './constants';
declare global {
@@ -36,38 +44,49 @@ declare global {
const OS_TYPE = osType();
const resolvePath = (fp: string, base: BaseDir): { baseDir: number; base: BaseDir; fp: string } => {
// Usage:
// 1. baseDir + fp
// 2. basePrefix + fp or the getPrefix util in FileSystem
const resolvePath = (path: string, base: BaseDir): ResolvedPath => {
switch (base) {
case 'Settings':
return { baseDir: BaseDirectory.AppConfig, fp, base };
return { baseDir: BaseDirectory.AppConfig, basePrefix: appConfigDir, fp: path, base };
case 'Data':
return { baseDir: BaseDirectory.AppData, fp, base };
return { baseDir: BaseDirectory.AppData, basePrefix: appDataDir, fp: path, base };
case 'Cache':
return { baseDir: BaseDirectory.AppCache, fp, base };
return { baseDir: BaseDirectory.AppCache, basePrefix: appCacheDir, fp: path, base };
case 'Log':
return { baseDir: BaseDirectory.AppLog, fp, base };
return { baseDir: BaseDirectory.AppLog, basePrefix: appLogDir, fp: path, base };
case 'Books':
return {
baseDir: BaseDirectory.AppData,
fp: `${LOCAL_BOOKS_SUBDIR}/${fp}`,
basePrefix: appDataDir,
fp: `${LOCAL_BOOKS_SUBDIR}/${path}`,
base,
};
case 'None':
return {
baseDir: 0,
fp,
basePrefix: async () => '',
fp: path,
base,
};
default:
return {
baseDir: BaseDirectory.Temp,
fp,
basePrefix: tempDir,
fp: path,
base,
};
}
};
export const nativeFileSystem: FileSystem = {
async getPrefix(base: BaseDir) {
const { basePrefix, fp } = resolvePath('', base);
const basePath = await basePrefix();
return fp ? await join(basePath, fp) : basePath;
},
getURL(path: string) {
return isValidURL(path) ? path : convertFileSrc(path);
},
@@ -89,8 +108,8 @@ export const nativeFileSystem: FileSystem = {
} else {
// Otherwise, for content:// URIs (e.g. from MediaStore, Drive, or third-party apps),
// we cannot access the file directly — so we copy it to a temporary cache location.
const prefix = this.getPrefix('Cache');
const dst = `${prefix}/${fname}`;
const prefix = await this.getPrefix('Cache');
const dst = await join(prefix, fname);
const res = await copyURIToPath({ uri: path, dst });
if (!res.success) {
console.error('Failed to open file:', res);
@@ -99,8 +118,11 @@ export const nativeFileSystem: FileSystem = {
return await new NativeFile(dst, fname, base ? baseDir : null).open();
}
} else {
const prefix = this.getPrefix(base);
const absolutePath = path.startsWith('/') ? path : prefix ? await join(prefix, path) : null;
const prefix = await this.getPrefix(base);
if (isFileURI(path)) {
path = path.replace(/^file:\/\//, '');
}
const absolutePath = path.startsWith('/') ? path : await join(prefix, path);
if (absolutePath && OS_TYPE !== 'android') {
// NOTE: RemoteFile currently performs about 2× faster than NativeFile
// due to an unresolved performance issue in Tauri (see tauri-apps/tauri#9190).
@@ -114,13 +136,13 @@ export const nativeFileSystem: FileSystem = {
},
async copyFile(srcPath: string, dstPath: string, base: BaseDir) {
if (isContentURI(srcPath)) {
const prefix = this.getPrefix(base);
const prefix = await this.getPrefix(base);
if (!prefix) {
throw new Error('Invalid base directory');
}
const res = await copyURIToPath({
uri: srcPath,
dst: `${prefix}/${dstPath}`,
dst: await join(prefix, dstPath),
});
if (!res.success) {
console.error('Failed to copy file:', res);
@@ -201,9 +223,6 @@ export const nativeFileSystem: FileSystem = {
return false;
}
},
getPrefix() {
return null;
},
};
export class NativeAppService extends BaseAppService {
@@ -232,18 +251,10 @@ export class NativeAppService extends BaseAppService {
(OS_TYPE === 'ios' && getOSPlatform() === 'ios') || OS_TYPE === 'android';
override distChannel = process.env['NEXT_PUBLIC_DIST_CHANNEL'] || 'readest';
override resolvePath(fp: string, base: BaseDir): { baseDir: number; base: BaseDir; fp: string } {
override resolvePath(fp: string, base: BaseDir): ResolvedPath {
return resolvePath(fp, base);
}
async getInitBooksDir(): Promise<string> {
return join(await appDataDir(), LOCAL_BOOKS_SUBDIR);
}
async getCacheDir(): Promise<string> {
return await appCacheDir();
}
async selectDirectory(): Promise<string> {
const selected = await openDialog({
directory: true,