forked from akai/readest
Refactor nativeAppService for further appServices
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import { AppService, ToastType } from '@/types/system';
|
||||
|
||||
import { SystemSettings } from '@/types/settings';
|
||||
import { FileSystem, BaseDir } from '@/types/system';
|
||||
import { Book, BookConfig, BookContent, BookFormat } from '@/types/book';
|
||||
import {
|
||||
getDir,
|
||||
getFilename,
|
||||
getBaseFilename,
|
||||
getCoverFilename,
|
||||
getConfigFilename,
|
||||
getLibraryFilename,
|
||||
INIT_BOOK_CONFIG,
|
||||
} from '@/utils/book';
|
||||
import { RemoteFile } from '@/utils/file';
|
||||
import { partialMD5 } from '@/utils/md5';
|
||||
import { BookDoc, DocumentLoader } from '@/libs/document';
|
||||
|
||||
export abstract class BaseAppService implements AppService {
|
||||
localBooksDir: string = '';
|
||||
abstract isAppDataSandbox: boolean;
|
||||
abstract fs: FileSystem;
|
||||
|
||||
abstract resolvePath(fp: string, base: BaseDir): { baseDir: number; base: BaseDir; fp: string };
|
||||
abstract getCoverImageUrl(book: Book): string;
|
||||
abstract getInitBooksDir(): Promise<string>;
|
||||
abstract selectDirectory(title: string): Promise<string>;
|
||||
abstract selectFiles(name: string, extensions: string[]): Promise<string[]>;
|
||||
abstract showMessage(
|
||||
msg: string,
|
||||
kind?: ToastType,
|
||||
title?: string,
|
||||
okLabel?: string,
|
||||
): Promise<void>;
|
||||
|
||||
async loadSettings(): Promise<SystemSettings> {
|
||||
let settings: SystemSettings;
|
||||
const { fp, base } = this.resolvePath('settings.json', 'Settings');
|
||||
|
||||
try {
|
||||
await this.fs.exists(fp, base);
|
||||
const txt = await this.fs.readFile(fp, base, 'text');
|
||||
settings = JSON.parse(txt as string);
|
||||
if (this.isAppDataSandbox) {
|
||||
settings.localBooksDir = await this.getInitBooksDir();
|
||||
}
|
||||
} catch {
|
||||
settings = {
|
||||
localBooksDir: await this.getInitBooksDir(),
|
||||
globalReadSettings: {
|
||||
themeType: 'auto',
|
||||
fontFamily: '',
|
||||
fontSize: 1.0,
|
||||
wordSpacing: 0.16,
|
||||
lineSpacing: 1.5,
|
||||
},
|
||||
};
|
||||
|
||||
await this.fs.createDir('', 'Books', true);
|
||||
await this.fs.createDir('', base, true);
|
||||
await this.fs.writeFile(fp, base, JSON.stringify(settings));
|
||||
}
|
||||
|
||||
this.localBooksDir = settings.localBooksDir;
|
||||
return settings;
|
||||
}
|
||||
|
||||
async saveSettings(settings: SystemSettings): Promise<void> {
|
||||
const { fp, base } = this.resolvePath('settings.json', 'Settings');
|
||||
await this.fs.createDir('', base, true);
|
||||
await this.fs.writeFile(fp, base, JSON.stringify(settings));
|
||||
}
|
||||
|
||||
async importBook(
|
||||
file: string | File,
|
||||
books: Book[],
|
||||
overwrite: boolean = false,
|
||||
): Promise<Book[]> {
|
||||
try {
|
||||
let loadedBook: BookDoc;
|
||||
let format: BookFormat;
|
||||
let filename: string;
|
||||
let fileobj: File;
|
||||
|
||||
try {
|
||||
if (typeof file === 'string') {
|
||||
filename = file;
|
||||
fileobj = await new RemoteFile(this.fs.getURL(file), file).open();
|
||||
} else {
|
||||
filename = file.name;
|
||||
fileobj = file;
|
||||
}
|
||||
({ book: loadedBook, format } = await new DocumentLoader(fileobj).open());
|
||||
if (!loadedBook.metadata.title) {
|
||||
loadedBook.metadata.title = getBaseFilename(filename);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error(`Failed to open the book: ${(error as Error).message || error}`);
|
||||
}
|
||||
|
||||
const hash = await partialMD5(fileobj);
|
||||
const existingBook = books.filter((b) => b.hash === hash)[0];
|
||||
if (existingBook) {
|
||||
if (existingBook.isRemoved) {
|
||||
delete existingBook.isRemoved;
|
||||
}
|
||||
existingBook.lastUpdated = Date.now();
|
||||
}
|
||||
|
||||
const book: Book = {
|
||||
hash,
|
||||
format,
|
||||
title: loadedBook.metadata.title,
|
||||
author: loadedBook.metadata.author,
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
book.coverImageUrl = this.getCoverImageUrl(book);
|
||||
|
||||
if (!(await this.fs.exists(getDir(book), 'Books'))) {
|
||||
await this.fs.createDir(getDir(book), 'Books');
|
||||
}
|
||||
if (!(await this.fs.exists(getFilename(book), 'Books')) || overwrite) {
|
||||
if (typeof file === 'string') {
|
||||
await this.fs.copyFile(file, getFilename(book), 'Books');
|
||||
} else {
|
||||
await this.fs.writeFile(getFilename(book), 'Books', await file.arrayBuffer());
|
||||
}
|
||||
}
|
||||
if (!(await this.fs.exists(getCoverFilename(book), 'Books')) || overwrite) {
|
||||
const cover = await loadedBook.getCover();
|
||||
if (cover) {
|
||||
await this.fs.writeFile(getCoverFilename(book), 'Books', await cover.arrayBuffer());
|
||||
}
|
||||
}
|
||||
// Never overwrite the config file only when it's not existed
|
||||
if (!existingBook) {
|
||||
await this.saveBookConfig(book, INIT_BOOK_CONFIG);
|
||||
books.splice(0, 0, book);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return books;
|
||||
}
|
||||
|
||||
async loadBookContent(book: Book): Promise<BookContent> {
|
||||
const fp = getFilename(book);
|
||||
let file: File;
|
||||
if (fp.endsWith('.pdf')) {
|
||||
const content = await this.fs.readFile(fp, 'Books', 'binary');
|
||||
file = new File([content], fp, { type: 'application/pdf' });
|
||||
} else {
|
||||
file = await new RemoteFile(this.fs.getURL(`${this.localBooksDir}/${fp}`), fp).open();
|
||||
}
|
||||
return { book, file, config: await this.loadBookConfig(book) };
|
||||
}
|
||||
|
||||
async loadBookConfig(book: Book): Promise<BookConfig> {
|
||||
try {
|
||||
const str = await this.fs.readFile(getConfigFilename(book), 'Books', 'text');
|
||||
return JSON.parse(str as string);
|
||||
} catch {
|
||||
return INIT_BOOK_CONFIG;
|
||||
}
|
||||
}
|
||||
|
||||
async saveBookConfig(book: Book, config: BookConfig) {
|
||||
await this.fs.writeFile(getConfigFilename(book), 'Books', JSON.stringify(config));
|
||||
}
|
||||
|
||||
async loadLibraryBooks(): Promise<Book[]> {
|
||||
let books: Book[] = [];
|
||||
const libraryFilename = getLibraryFilename();
|
||||
|
||||
try {
|
||||
const txt = await this.fs.readFile(libraryFilename, 'Books', 'text');
|
||||
books = JSON.parse(txt as string);
|
||||
} catch {
|
||||
await this.fs.writeFile(libraryFilename, 'Books', '[]');
|
||||
}
|
||||
|
||||
books.forEach((book) => {
|
||||
book.coverImageUrl = this.getCoverImageUrl(book);
|
||||
});
|
||||
|
||||
return books;
|
||||
}
|
||||
|
||||
async saveLibraryBooks(books: Book[]): Promise<void> {
|
||||
await this.fs.writeFile(getLibraryFilename(), 'Books', JSON.stringify(books));
|
||||
}
|
||||
|
||||
async updateLibraryBook(book: Book): Promise<void> {
|
||||
const library = await this.loadLibraryBooks();
|
||||
const bookIndex = library.findIndex((b) => b.hash === book.hash);
|
||||
if (bookIndex !== -1) {
|
||||
library[bookIndex] = book;
|
||||
}
|
||||
await this.saveLibraryBooks(library);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppService } from '../types/system';
|
||||
import { AppService } from '@/types/system';
|
||||
|
||||
export interface EnvConfigType {
|
||||
appService: () => Promise<AppService>;
|
||||
@@ -6,8 +6,10 @@ export interface EnvConfigType {
|
||||
|
||||
const environmentConfig: EnvConfigType = {
|
||||
appService: async () => {
|
||||
const { nativeAppService } = await import('./nativeAppService');
|
||||
return nativeAppService;
|
||||
const { NativeAppService } = await import('@/services/nativeAppService');
|
||||
const appService = new NativeAppService();
|
||||
await appService.loadSettings();
|
||||
return appService;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,199 +1,176 @@
|
||||
import {
|
||||
exists,
|
||||
mkdir,
|
||||
readTextFile,
|
||||
readFile,
|
||||
writeTextFile,
|
||||
writeFile,
|
||||
readDir,
|
||||
remove,
|
||||
copyFile,
|
||||
BaseDirectory,
|
||||
} from '@tauri-apps/plugin-fs';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { open, message } from '@tauri-apps/plugin-dialog';
|
||||
import { join, documentDir } from '@tauri-apps/api/path';
|
||||
import { join, documentDir, appDataDir } from '@tauri-apps/api/path';
|
||||
import { type as osType } from '@tauri-apps/plugin-os';
|
||||
|
||||
import { Book, BookConfig, BookFormat } from '../types/book';
|
||||
import { SystemSettings } from '../types/settings';
|
||||
import { AppService, ToastType } from '../types/system';
|
||||
import { Book } from '@/types/book';
|
||||
import { ToastType, FileSystem, BaseDir } from '@/types/system';
|
||||
import { getCoverFilename } from '@/utils/book';
|
||||
|
||||
import { BaseAppService } from './appService';
|
||||
import { LOCAL_BOOKS_SUBDIR } from './constants';
|
||||
import { resolvePath, nativeFileSystem } from './nativeFileSystem';
|
||||
import {
|
||||
getBaseFilename,
|
||||
getConfigFilename,
|
||||
getCoverFilename,
|
||||
getDir,
|
||||
getFilename,
|
||||
getLibraryFilename,
|
||||
INIT_BOOK_CONFIG,
|
||||
} from '@/utils/book';
|
||||
import { BookDoc, DocumentLoader } from '@/libs/document';
|
||||
import { RemoteFile } from '@/utils/file';
|
||||
import { partialMD5 } from '@/utils/md5';
|
||||
|
||||
let BOOKS_DIR = '';
|
||||
export const isMobile = ['android', 'ios'].includes(osType());
|
||||
|
||||
const SETTINGS_PATH = resolvePath('settings.json', 'Settings');
|
||||
export const resolvePath = (
|
||||
fp: string,
|
||||
base: BaseDir,
|
||||
): { baseDir: number; base: BaseDir; fp: string } => {
|
||||
switch (base) {
|
||||
case 'Settings':
|
||||
return { baseDir: BaseDirectory.AppConfig, fp, base };
|
||||
case 'Data':
|
||||
return { baseDir: BaseDirectory.AppData, fp, base };
|
||||
case 'Cache':
|
||||
return { baseDir: BaseDirectory.AppCache, fp, base };
|
||||
case 'Log':
|
||||
return { baseDir: BaseDirectory.AppLog, fp, base };
|
||||
case 'Books':
|
||||
return {
|
||||
baseDir: isMobile ? BaseDirectory.AppData : BaseDirectory.Document,
|
||||
fp: `${LOCAL_BOOKS_SUBDIR}/${fp}`,
|
||||
base,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
baseDir: BaseDirectory.Temp,
|
||||
fp,
|
||||
base,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const nativeAppService: AppService = {
|
||||
fs: nativeFileSystem,
|
||||
loadSettings: async () => {
|
||||
let settings: SystemSettings;
|
||||
const { fp, base } = SETTINGS_PATH;
|
||||
export const nativeFileSystem: FileSystem = {
|
||||
getURL(path: string) {
|
||||
return convertFileSrc(path);
|
||||
},
|
||||
async copyFile(srcPath: string, dstPath: string, base: BaseDir) {
|
||||
const { fp, baseDir } = resolvePath(dstPath, base);
|
||||
await copyFile(srcPath, fp, base && { toPathBaseDir: baseDir });
|
||||
},
|
||||
async readFile(path: string, base: BaseDir, mode: 'text' | 'binary') {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
return mode === 'text'
|
||||
? (readTextFile(fp, base && { baseDir }) as Promise<string>)
|
||||
: ((await readFile(fp, base && { baseDir })).buffer as ArrayBuffer);
|
||||
},
|
||||
async writeFile(path: string, base: BaseDir, content: string | ArrayBuffer) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
return typeof content === 'string'
|
||||
? writeTextFile(fp, content, base && { baseDir })
|
||||
: writeFile(fp, new Uint8Array(content), base && { baseDir });
|
||||
},
|
||||
async removeFile(path: string, base: BaseDir) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
return remove(fp, base && { baseDir });
|
||||
},
|
||||
async createDir(path: string, base: BaseDir, recursive = false) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
await mkdir(fp, base && { baseDir, recursive });
|
||||
},
|
||||
async removeDir(path: string, base: BaseDir, recursive = false) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
await remove(fp, base && { baseDir, recursive });
|
||||
},
|
||||
async readDir(path: string, base: BaseDir) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
const list = await readDir(fp, base && { baseDir });
|
||||
return list.map((entity) => {
|
||||
return {
|
||||
path: entity.name,
|
||||
isDir: entity.isDirectory,
|
||||
};
|
||||
});
|
||||
},
|
||||
async exists(path: string, base: BaseDir) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
try {
|
||||
await nativeAppService.fs.exists(fp, base);
|
||||
const txt = await nativeAppService.fs.readFile(fp, base, 'text');
|
||||
settings = JSON.parse(txt as string);
|
||||
const res = await exists(fp, base && { baseDir });
|
||||
return res;
|
||||
} catch {
|
||||
const INIT_BOOKS_DIR = await join(await documentDir(), LOCAL_BOOKS_SUBDIR);
|
||||
await nativeAppService.fs.createDir('', 'Books', true);
|
||||
settings = {
|
||||
localBooksDir: INIT_BOOKS_DIR,
|
||||
globalReadSettings: {
|
||||
themeType: 'auto',
|
||||
fontFamily: '',
|
||||
fontSize: 1.0,
|
||||
wordSpacing: 0.16,
|
||||
lineSpacing: 1.5,
|
||||
},
|
||||
};
|
||||
|
||||
await nativeAppService.fs.createDir('', base, true);
|
||||
await nativeAppService.fs.writeFile(fp, base, JSON.stringify(settings));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
BOOKS_DIR = settings.localBooksDir;
|
||||
return settings;
|
||||
},
|
||||
saveSettings: async (settings: SystemSettings) => {
|
||||
const { fp, base } = SETTINGS_PATH;
|
||||
await nativeAppService.fs.createDir('', base, true);
|
||||
await nativeAppService.fs.writeFile(fp, base, JSON.stringify(settings));
|
||||
BOOKS_DIR = settings.localBooksDir;
|
||||
},
|
||||
selectDirectory: async (title: string) => {
|
||||
export class NativeAppService extends BaseAppService {
|
||||
fs = nativeFileSystem;
|
||||
isAppDataSandbox = isMobile;
|
||||
|
||||
resolvePath(fp: string, base: BaseDir): { baseDir: number; base: BaseDir; fp: string } {
|
||||
switch (base) {
|
||||
case 'Settings':
|
||||
return { baseDir: BaseDirectory.AppConfig, fp, base };
|
||||
case 'Data':
|
||||
return { baseDir: BaseDirectory.AppData, fp, base };
|
||||
case 'Cache':
|
||||
return { baseDir: BaseDirectory.AppCache, fp, base };
|
||||
case 'Log':
|
||||
return { baseDir: BaseDirectory.AppLog, fp, base };
|
||||
case 'Books':
|
||||
return {
|
||||
baseDir: BaseDirectory.Document,
|
||||
fp: `${LOCAL_BOOKS_SUBDIR}/${fp}`,
|
||||
base,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
baseDir: BaseDirectory.Temp,
|
||||
fp,
|
||||
base,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getInitBooksDir(): Promise<string> {
|
||||
return join(appDataDir ? await appDataDir() : await documentDir(), LOCAL_BOOKS_SUBDIR);
|
||||
}
|
||||
|
||||
async selectDirectory(title: string): Promise<string> {
|
||||
const selected = await open({
|
||||
title,
|
||||
directory: true,
|
||||
});
|
||||
|
||||
return selected as string;
|
||||
},
|
||||
selectFiles: async (name: string, extensions: string[]) => {
|
||||
}
|
||||
|
||||
async selectFiles(name: string, extensions: string[]): Promise<string[]> {
|
||||
const selected = await open({
|
||||
multiple: true,
|
||||
filters: [{ name, extensions }],
|
||||
});
|
||||
return Array.isArray(selected) ? selected : selected ? [selected] : [];
|
||||
}
|
||||
|
||||
if (Array.isArray(selected)) {
|
||||
return selected;
|
||||
} else if (selected === null) {
|
||||
return [];
|
||||
} else {
|
||||
return [selected];
|
||||
}
|
||||
},
|
||||
showMessage: async (msg: string, kind: ToastType = 'info', title?: string, okLabel?: string) => {
|
||||
async showMessage(
|
||||
msg: string,
|
||||
kind: ToastType = 'info',
|
||||
title?: string,
|
||||
okLabel?: string,
|
||||
): Promise<void> {
|
||||
await message(msg, { kind, title, okLabel });
|
||||
},
|
||||
importBook: async (
|
||||
file: string | File,
|
||||
books: Book[],
|
||||
overwrite: boolean = false,
|
||||
): Promise<Book[]> => {
|
||||
try {
|
||||
let loadedBook: BookDoc;
|
||||
let format: BookFormat;
|
||||
let filename: string;
|
||||
let fileobj: File;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof file === 'string') {
|
||||
filename = file;
|
||||
fileobj = await new RemoteFile(nativeAppService.fs.getURL(file), file).open();
|
||||
} else {
|
||||
filename = file.name;
|
||||
fileobj = file;
|
||||
}
|
||||
({ book: loadedBook, format } = await new DocumentLoader(fileobj).open());
|
||||
if (!loadedBook.metadata.title) {
|
||||
loadedBook.metadata.title = getBaseFilename(filename);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error(`Failed to open the book: ${(error as Error).message || error}`);
|
||||
}
|
||||
|
||||
const hash = await partialMD5(fileobj);
|
||||
const existingBook = books.filter((b) => b.hash === hash)[0];
|
||||
if (existingBook) {
|
||||
if (existingBook.isRemoved) {
|
||||
delete existingBook.isRemoved;
|
||||
}
|
||||
existingBook.lastUpdated = Date.now();
|
||||
}
|
||||
|
||||
const book: Book = {
|
||||
hash,
|
||||
format,
|
||||
title: loadedBook.metadata.title,
|
||||
author: loadedBook.metadata.author,
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
book.coverImageUrl = nativeAppService.getCoverImageUrl(book);
|
||||
|
||||
if (!(await nativeAppService.fs.exists(getDir(book), 'Books'))) {
|
||||
await nativeAppService.fs.createDir(getDir(book), 'Books');
|
||||
}
|
||||
if (!(await nativeAppService.fs.exists(getFilename(book), 'Books')) || overwrite) {
|
||||
if (typeof file === 'string') {
|
||||
await nativeAppService.fs.copyFile(file, getFilename(book), 'Books');
|
||||
} else {
|
||||
await nativeAppService.fs.writeFile(getFilename(book), 'Books', await file.arrayBuffer());
|
||||
}
|
||||
}
|
||||
if (!(await nativeAppService.fs.exists(getCoverFilename(book), 'Books')) || overwrite) {
|
||||
const cover = await loadedBook.getCover();
|
||||
if (cover) {
|
||||
await nativeAppService.fs.writeFile(
|
||||
getCoverFilename(book),
|
||||
'Books',
|
||||
await cover.arrayBuffer(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Never overwrite the config file only when it's not existed
|
||||
if (!existingBook) {
|
||||
await nativeAppService.saveBookConfig(book, INIT_BOOK_CONFIG);
|
||||
books.splice(0, 0, book);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return books;
|
||||
},
|
||||
loadBookConfig: async (book: Book): Promise<BookConfig> => {
|
||||
try {
|
||||
const str = await nativeAppService.fs.readFile(getConfigFilename(book), 'Books', 'text');
|
||||
return JSON.parse(str as string);
|
||||
} catch {
|
||||
return INIT_BOOK_CONFIG;
|
||||
}
|
||||
},
|
||||
saveBookConfig: async (book: Book, config: BookConfig) => {
|
||||
await nativeAppService.fs.writeFile(getConfigFilename(book), 'Books', JSON.stringify(config));
|
||||
},
|
||||
loadLibraryBooks: async () => {
|
||||
let books: Book[] = [];
|
||||
const libraryFilename = getLibraryFilename();
|
||||
try {
|
||||
const txt = await nativeAppService.fs.readFile(libraryFilename, 'Books', 'text');
|
||||
books = JSON.parse(txt as string);
|
||||
} catch {
|
||||
await nativeAppService.fs.writeFile(libraryFilename, 'Books', '[]');
|
||||
}
|
||||
|
||||
books.forEach((book) => {
|
||||
book.coverImageUrl = nativeAppService.getCoverImageUrl(book);
|
||||
});
|
||||
|
||||
return books;
|
||||
},
|
||||
saveLibraryBooks: async (books: Book[]) => {
|
||||
await nativeAppService.fs.writeFile(getLibraryFilename(), 'Books', JSON.stringify(books));
|
||||
},
|
||||
getCoverImageUrl: (book: Book) => {
|
||||
return convertFileSrc(`${BOOKS_DIR}/${getCoverFilename(book)}`);
|
||||
},
|
||||
};
|
||||
getCoverImageUrl = (book: Book): string => {
|
||||
return this.fs.getURL(`${this.localBooksDir}/${getCoverFilename(book)}`);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import {
|
||||
exists,
|
||||
mkdir,
|
||||
readTextFile,
|
||||
readFile,
|
||||
writeTextFile,
|
||||
writeFile,
|
||||
readDir,
|
||||
remove,
|
||||
BaseDirectory,
|
||||
copyFile,
|
||||
} from '@tauri-apps/plugin-fs';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
|
||||
import { FileSystem, BaseDir } from '../types/system';
|
||||
import { LOCAL_BOOKS_SUBDIR } from './constants';
|
||||
|
||||
export const resolvePath = (
|
||||
fp: string,
|
||||
base: BaseDir,
|
||||
): { baseDir: number; base: BaseDir; fp: string } => {
|
||||
switch (base) {
|
||||
case 'Settings':
|
||||
return { baseDir: BaseDirectory.AppConfig, fp, base };
|
||||
case 'Data':
|
||||
return { baseDir: BaseDirectory.AppData, fp, base };
|
||||
case 'Cache':
|
||||
return { baseDir: BaseDirectory.AppCache, fp, base };
|
||||
case 'Log':
|
||||
return { baseDir: BaseDirectory.AppLog, fp, base };
|
||||
case 'Books':
|
||||
return {
|
||||
baseDir: BaseDirectory.Document,
|
||||
fp: `${LOCAL_BOOKS_SUBDIR}/${fp}`,
|
||||
base,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
baseDir: BaseDirectory.Temp,
|
||||
fp,
|
||||
base,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const nativeFileSystem: FileSystem = {
|
||||
getURL(path: string) {
|
||||
return convertFileSrc(path);
|
||||
},
|
||||
async copyFile(srcPath: string, dstPath: string, base: BaseDir) {
|
||||
const { fp, baseDir } = resolvePath(dstPath, base);
|
||||
await copyFile(srcPath, fp, base && { toPathBaseDir: baseDir });
|
||||
},
|
||||
async readFile(path: string, base: BaseDir, mode: 'text' | 'binary') {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
return mode === 'text'
|
||||
? (readTextFile(fp, base && { baseDir }) as Promise<string>)
|
||||
: ((await readFile(fp, base && { baseDir })).buffer as ArrayBuffer);
|
||||
},
|
||||
async writeFile(path: string, base: BaseDir, content: string | ArrayBuffer) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
return typeof content === 'string'
|
||||
? writeTextFile(fp, content, base && { baseDir })
|
||||
: writeFile(fp, new Uint8Array(content), base && { baseDir });
|
||||
},
|
||||
async removeFile(path: string, base: BaseDir) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
return remove(fp, base && { baseDir });
|
||||
},
|
||||
async createDir(path: string, base: BaseDir, recursive = false) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
await mkdir(fp, base && { baseDir, recursive });
|
||||
},
|
||||
async removeDir(path: string, base: BaseDir, recursive = false) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
await remove(fp, base && { baseDir, recursive });
|
||||
},
|
||||
async readDir(path: string, base: BaseDir) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
const list = await readDir(fp, base && { baseDir });
|
||||
return list.map((entity) => {
|
||||
return {
|
||||
path: entity.name,
|
||||
isDir: entity.isDirectory,
|
||||
};
|
||||
});
|
||||
},
|
||||
async exists(path: string, base: BaseDir) {
|
||||
const { fp, baseDir } = resolvePath(path, base);
|
||||
|
||||
try {
|
||||
const res = await exists(fp, base && { baseDir });
|
||||
return res;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -37,3 +37,8 @@ export interface BooksGroup {
|
||||
books: Book[];
|
||||
lastUpdated: number;
|
||||
}
|
||||
export interface BookContent {
|
||||
book: Book;
|
||||
file: File;
|
||||
config: BookConfig;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SystemSettings } from './settings';
|
||||
import { Book, BookConfig } from './book';
|
||||
import { Book, BookConfig, BookContent } from './book';
|
||||
|
||||
export type BaseDir = 'Books' | 'Settings' | 'Data' | 'Log' | 'Cache' | 'None';
|
||||
export type ToastType = 'info' | 'warning' | 'error';
|
||||
@@ -19,15 +19,16 @@ export interface FileSystem {
|
||||
export interface AppService {
|
||||
fs: FileSystem;
|
||||
|
||||
loadSettings(): Promise<SystemSettings>;
|
||||
saveSettings(settings: SystemSettings): Promise<void>;
|
||||
selectDirectory(title: string): Promise<string>;
|
||||
selectFiles(name: string, extensions: string[]): Promise<string[]>;
|
||||
showMessage(msg: string, kind?: ToastType, title?: string, okLabel?: string): Promise<void>;
|
||||
|
||||
loadSettings(): Promise<SystemSettings>;
|
||||
saveSettings(settings: SystemSettings): Promise<void>;
|
||||
importBook(file: string | File, books: Book[], overwrite?: boolean): Promise<Book[]>;
|
||||
loadBookConfig(book: Book): Promise<BookConfig>;
|
||||
saveBookConfig(book: Book, config: BookConfig): Promise<void>;
|
||||
loadBookContent(book: Book): Promise<BookContent>;
|
||||
loadLibraryBooks(): Promise<Book[]>;
|
||||
saveLibraryBooks(books: Book[]): Promise<void>;
|
||||
getCoverImageUrl(book: Book): string;
|
||||
|
||||
Reference in New Issue
Block a user