-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.ts
252 lines (225 loc) · 9.29 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import { zip, unzip, subscribe, } from 'react-native-zip-archive'
import EpubFile, { EpubSettings, File, EpubChapter, EpubSettingsLoader, Parameter, jsonExtractor, bodyExtrator } from 'epub-constructor'
import { v4 as uuidv4 } from 'uuid';
export type ReadDirItem = {
path: string // The absolute path to the item
isFile: () => boolean // Is the file just a file?
isDirectory: () => boolean // Is the file a directory?
}
export type {
File,
EpubChapter,
EpubSettings,
Parameter,
EpubSettingsLoader,
jsonExtractor,
bodyExtrator
}
export const getValidFileNameByTitle = (title: string) => {
return title.replace(/[^a-zA-Z0-9 ]/g, "");
}
const getEpubfileName = (name: string) => {
if (name.endsWith(".epub"))
return name;
else return name + ".epub";
}
/*
file reader settings
best use with react-native-fs
*/
export interface FsSettings {
writeFile: (filepath: string, contents: string, encodingOrOptions?: any) => Promise<void>;
mkdir: (filePath: string) => Promise<void>;
unlink: (filePath: string) => Promise<void>;
exists: (filePath: string) => Promise<boolean>;
readFile: (filePath: string, encodingOrOptions?: any) => Promise<string>;
readDir: (filePath: string) => Promise<ReadDirItem[]>;
}
const checkFile = (path: string) => {
var name = path.split("/").reverse()[0].toLocaleLowerCase();
var fileExtension = [".json", ".html", ".xml", ".opf", ".ncx", ".css", "mimetype", ".epub"]
var fileInfo = {
isDirectory: !fileExtension.find(x => name.indexOf(x) !== -1),
folderPath: path.split("/").length > 1 && fileExtension.find(x => name.indexOf(x) !== -1) ? path.split("/").reverse().filter((x, index) => index > 0).reverse().join("/") : path
}
return fileInfo;
}
const validateDir = async (path: string, reader: FsSettings) => {
path = getFolderPath(path);
if (!(await reader.exists(path))) {
await reader.mkdir(path);
}
}
const getFolderPath = (path: string) => {
var file = checkFile(path);
return file.folderPath;
}
const getFiles = async (folder: string, reader: FsSettings) => {
var fs = await reader.readDir(folder);
var files = [] as ReadDirItem[];
for (var f of fs) {
if (f.isFile())
files.push(f);
else if (f.isDirectory()) {
files = [...files, ...(await getFiles(f.path, reader))]
}
}
return files;
}
export const EpubLoader = async (epubPath: string, RNFS: FsSettings, localOnProgress?: (progress: number, file: string) => void, onEpubExtractionsProgress?: (progress: number, file: string) => void) => {
if (!await RNFS.exists(epubPath))
throw "Epub File could not be found.";
var sub = subscribe(({ progress, filePath }: { progress: number, filePath: string }) => {
if (filePath === epubPath) {
onEpubExtractionsProgress?.(progress * 100, epubPath);
}
});
const destinationFolder = getFolderPath(epubPath)
const tempFolder = destinationFolder + "/" + uuidv4();
try {
await validateDir(tempFolder, RNFS);
await unzip(epubPath, tempFolder, "UTF-8");
const epubFiles = [] as File[];
const files = await getFiles(tempFolder, RNFS);
const len = files.length + 1;
var dProgress = 0;
const jsonFile = files.find(x => x.path.endsWith(".json"));
if (!jsonFile) {
for (var i = 0; i < files.length; i++) {
var f = files[i];
dProgress = ((i / parseFloat(len.toString())) * 100)
if (f.isFile()) {
var file = {
path: f.path.replace(tempFolder, ""),
content: await RNFS.readFile(f.path)
} as File
epubFiles.push(file);
EpubBuilder.onProgress?.(dProgress, epubPath, "Reading");
localOnProgress?.(dProgress, epubPath);
}
}
} else epubFiles.push({
path: jsonFile.path.replace(tempFolder, ""),
content: await RNFS.readFile(jsonFile.path)
} as File)
sub.remove();
await RNFS.unlink(tempFolder);
dProgress = ((len / parseFloat(len.toString())) * 100)
var item = new EpubBuilder(await EpubSettingsLoader(epubFiles, (p) => {
onEpubExtractionsProgress?.(p, epubPath);
}), destinationFolder, RNFS);
EpubBuilder.onProgress?.(dProgress, epubPath, "Reading");
localOnProgress?.(dProgress, epubPath);
return item
} catch (e) {
throw e
} finally {
sub.remove();
}
}
export default class EpubBuilder {
private settings: EpubSettings;
static onProgress?: (progress: number, epubFile: string, operation: "constructEpub" | "SaveFile" | "LoadingFile" | "Zip" | "Unzip" | "Reading") => void;
private destinationFolderPath?: string;
private tempPath?: string;
private RNFS: FsSettings;
private dProgress: number = 0;
public onSaveProgress?: (progress: number, epubFile: string, operation: "constructEpub" | "SaveFile") => Promise<void>
/*
destinationFolderPath: destination to the folder, You could use react-native-fs RNFS.DownloadDirectoryPath
*/
constructor(settings: EpubSettings, destinationFolderPath: string, RNFS: FsSettings) {
this.settings = settings;
this.destinationFolderPath = getFolderPath(destinationFolderPath);
this.RNFS = RNFS;
}
public getEpubSettings() {
return this.settings;
}
/*
This will prepare a temp folder that containe the data of the epub file.
the folder will be descarded when the epub file is created eg save() or discardChanges()
*/
public async prepare() {
await this.createTempFolder();
return this;
}
/*
discard all changes
*/
public async discardChanges() {
try {
console.log("Removing:" + this.tempPath);
if (this.tempPath)
if (await this.RNFS.exists(this.tempPath))
await this.RNFS.unlink(this.tempPath);
this.tempPath = undefined;
} catch (error) {
console.log(error);
}
}
/*
add a new Chapter
*/
public async addChapter(epubChapter: EpubChapter) {
this.settings.chapters.push(epubChapter);
await this.createTempFolder();
}
/*
destinationFolderPath: destination to the folder, You could use react-native-fs RNFS.DownloadDirectoryPath
RNFS: file reader settings best use with react-native-fs eg import * as RNFS from 'react-native-fs', or you could use your own filereder
removeTempFile(default true) set to false if there will be other changes to the epub file so it wont have to recreate the temp folder
*/
public async save(removeTempFile?: boolean) {
const targetPath = `${this.destinationFolderPath}/${getEpubfileName(this.settings.fileName ?? this.settings.title)}`
await this.createTempFolder();
try {
if (await this.RNFS.exists(targetPath))
await this.RNFS.unlink(targetPath);
} catch (error) {
console.log("unable to delete the existing: " + targetPath)
}
if (this.tempPath)
await zip(this.tempPath, targetPath);
if (removeTempFile !== false)
await this.discardChanges();
return targetPath;
}
private async createTempFolder() {
const targetPath = `${this.destinationFolderPath}/${getEpubfileName(this.settings.fileName ?? this.settings.title)}`
var overrideFiles = ["toc.ncx", "toc.html", ".opf", ".json"]
const epub = new EpubFile(this.settings);
const files = await epub.constructEpub(async (progress) => {
if (this.onSaveProgress)
await this.onSaveProgress?.(progress, targetPath, "constructEpub")
});
this.tempPath = this.tempPath ?? (this.destinationFolderPath + "/" + uuidv4());
await validateDir(this.tempPath, this.RNFS);
this.dProgress = 0;
var len = files.length + 1;
for (var i = 0; i < files.length; i++) {
const x = files[i];
this.dProgress = ((i / parseFloat(len.toString())) * 100)
var path = this.tempPath + "/" + x.path;
if (overrideFiles.find(f => x.path.indexOf(f) != -1) && await this.RNFS.exists(path))
await this.RNFS.unlink(path);
var fileSettings = checkFile(x.path);
if (!fileSettings.isDirectory)
await validateDir(path, this.RNFS);
if (!await this.RNFS.exists(path))
await this.RNFS.writeFile(path, x.content, "utf8");
if (this.destinationFolderPath) {
EpubBuilder.onProgress?.(this.dProgress, targetPath, "SaveFile")
if (this.onSaveProgress)
await this.onSaveProgress?.(this.dProgress, targetPath, "SaveFile");
}
}
}
/*
epubPath: path to the epub file
RNFS: file reader settings best use with react-native-fs eg import * as RNFS from 'react-native-fs', or you could use your own filereder
*/
static async loadEpub(epubPath: string, RNFS: FsSettings, localOnProgress?: (progress: number, file: string) => void) {
return await EpubLoader(epubPath, RNFS, localOnProgress)
}
}