feat: add support for importing from a directory recursively, closes #179 (#2642)

This commit is contained in:
Huang Xin
2025-12-08 14:16:57 +08:00
committed by GitHub
parent 11bc7497e8
commit ba3f060cc4
44 changed files with 341 additions and 97 deletions
+10 -2
View File
@@ -98,6 +98,7 @@ export abstract class BaseAppService implements AppService {
hasScreenBrightness = false;
hasIAP = false;
canCustomizeRootDir = false;
canReadExternalDir = false;
distChannel = 'readest' as DistChannel;
protected CURRENT_MIGRATION_VERSION = 20251124;
@@ -354,7 +355,6 @@ export abstract class BaseAppService implements AppService {
loadedBook.metadata.title = getBaseFilename(filename);
}
} catch (error) {
console.error(error);
throw new Error(`Failed to open the book: ${(error as Error).message || error}`);
}
@@ -405,7 +405,14 @@ export abstract class BaseAppService implements AppService {
} else if (typeof file === 'string' && isContentURI(file)) {
await this.fs.copyFile(file, getLocalBookFilename(book), 'Books');
} else if (typeof file === 'string' && !isValidURL(file)) {
await this.fs.copyFile(file, getLocalBookFilename(book), 'Books');
try {
// try to copy the file directly first in case of large files to avoid memory issues
// on desktop when reading recursively from selected directory the direct copy will fail
// due to permission issues, then fallback to read and write files
await this.fs.copyFile(file, getLocalBookFilename(book), 'Books');
} catch {
await this.fs.writeFile(getLocalBookFilename(book), 'Books', fileobj);
}
} else {
await this.fs.writeFile(getLocalBookFilename(book), 'Books', fileobj);
}
@@ -441,6 +448,7 @@ export abstract class BaseAppService implements AppService {
return existingBook || book;
} catch (error) {
console.error('Error importing book:', error);
throw error;
}
}
@@ -34,7 +34,7 @@ import {
FileItem,
DistChannel,
} from '@/types/system';
import { getOSPlatform, isContentURI, isValidURL } from '@/utils/misc';
import { getOSPlatform, isContentURI, isFileURI, isValidURL } from '@/utils/misc';
import { getDirPath, getFilename } from '@/utils/path';
import { NativeFile, RemoteFile } from '@/utils/file';
import { copyURIToPath } from '@/utils/bridge';
@@ -212,17 +212,20 @@ export const nativeFileSystem: FileSystem = {
}
return await new NativeFile(dst, fname, baseDir ? baseDir : null).open();
}
} else if (isFileURI(path)) {
return await new NativeFile(fp, fname, baseDir ? baseDir : null).open();
} else {
const prefix = await this.getPrefix(base);
const absolutePath = path.startsWith('/') ? path : prefix ? await join(prefix, path) : null;
if (absolutePath && OS_TYPE !== 'android') {
if (OS_TYPE === 'android') {
// NOTE: RemoteFile is not usable on Android due to a known issue of range request in Android WebView.
// see https://issues.chromium.org/issues/40739128
return await new NativeFile(fp, fname, baseDir ? baseDir : null).open();
} else {
// NOTE: RemoteFile currently performs about 2× faster than NativeFile
// due to an unresolved performance issue in Tauri (see tauri-apps/tauri#9190).
// Once the bug is resolved, we should switch back to using NativeFile.
// RemoteFile is not usable on Android due to unknown issues of range fetch with Android WebView.
const prefix = await this.getPrefix(base);
const absolutePath = prefix ? await join(prefix, path) : path;
return await new RemoteFile(this.getURL(absolutePath), fname).open();
} else {
return await new NativeFile(fp, fname, baseDir ? baseDir : null).open();
}
}
},
@@ -319,10 +322,13 @@ export const nativeFileSystem: FileSystem = {
} else {
const filePath = await join(parent, entry.name);
const relativePath = relative ? await join(relative, entry.name) : entry.name;
const fileInfo = await stat(filePath, baseDir ? { baseDir } : undefined);
const opts = baseDir ? { baseDir } : undefined;
const fileSize = await stat(filePath, opts)
.then((info) => info.size)
.catch(() => 0);
fileList.push({
path: relativePath,
size: fileInfo.size,
size: fileSize,
});
}
}
@@ -374,6 +380,7 @@ export class NativeAppService extends BaseAppService {
// CustomizeRootDir has a blocker on macOS App Store builds due to Security Scoped Resource restrictions.
// See: https://github.com/tauri-apps/tauri/issues/3716
override canCustomizeRootDir = DIST_CHANNEL !== 'appstore';
override canReadExternalDir = DIST_CHANNEL !== 'appstore' && DIST_CHANNEL !== 'playstore';
override distChannel = DIST_CHANNEL;
private execDir?: string = undefined;