Skip to content

Commit

Permalink
Cheaper epub parsing (LNReader#1007)
Browse files Browse the repository at this point in the history
* cheaper epub parsing

* cleaner process

* no empty page list

* more epub detail

* fix nullable
  • Loading branch information
nyagami authored Mar 20, 2024
1 parent 7629454 commit 817e4dc
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 158 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package com.rajarsheechatterjee.EpubUtil;

import android.util.Xml;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;

public class EpubUtil extends ReactContextBaseJavaModule {
EpubUtil(ReactApplicationContext context){super(context);}

@NonNull
@Override
public String getName() {
return "EpubUtil";
}

private XmlPullParser initParse(File file) throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(new FileInputStream(file), null);
parser.nextTag();
return parser;
}

private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
String result = "";
if (parser.next() == XmlPullParser.TEXT) {
result = parser.getText();
parser.nextTag();
}
return result;
}

private String cleanUrl(String url){
if(url != null){
return url.replaceFirst("#[^.]+?$", "");
}
return null;
}

@ReactMethod
public void parseNovelAndChapters(String epubDirPath, Promise promise) {
try {
File containerFile = new File(epubDirPath, "META-INF/container.xml");
File contentMetaFile = new File(epubDirPath, getContentMetaFilePath(containerFile));
String contentDir = contentMetaFile.getParent();

ReadableMap novel = getNovelMetadata(contentMetaFile, contentDir);
promise.resolve(novel);
} catch (XmlPullParserException | IOException e) {
promise.reject(e.getCause());
}
}

private String getContentMetaFilePath(File file) throws XmlPullParserException, IOException {
XmlPullParser parser = initParse(file);
while (parser.next() != XmlPullParser.END_TAG){
@Nullable String tag = parser.getName();
if(tag != null && tag.equals("rootfile")) {
return parser.getAttributeValue(null, "full-path");
}
}
return "OEBPS/content.opf"; // default
}
private ReadableMap getNovelMetadata(File file, String contentDir) throws XmlPullParserException, IOException {
WritableMap novel = new WritableNativeMap();
WritableArray chapters = new WritableNativeArray();
XmlPullParser parser = initParse(file);
HashMap<String, String> refMap = new HashMap<>();
HashMap<String, String> tocMap = new HashMap<>();
File tocFile = new File(contentDir, "toc.ncx");
if(tocFile.exists()){
XmlPullParser tocParser = initParse(tocFile);
String label = null;
while (tocParser.next() != XmlPullParser.END_DOCUMENT){
String tag = tocParser.getName();
if(tag != null){
if(tag.equals("text")){
label = readText(tocParser);
}else if(tag.equals("content")){
String href = cleanUrl(tocParser.getAttributeValue(null, "src"));
if(href != null){
tocMap.put(href, label);
}
}
}
}
}
String cover = null;
while (parser.next() != XmlPullParser.END_DOCUMENT){
@Nullable String tag = parser.getName();
if(tag != null){
switch (tag) {
case "item": {
String id = parser.getAttributeValue(null, "id");
String href = parser.getAttributeValue(null, "href");
if (id != null) {
refMap.put(id, href);
}
break;
}
case "itemref": {
String idRef = parser.getAttributeValue(null, "idref");
String href = refMap.get(idRef);
if (href != null) {
WritableMap chapter = new WritableNativeMap();
chapter.putString("path", contentDir + "/" + href);
String name = tocMap.get(href);
chapter.putString("name", name == null ? href : name);
chapters.pushMap(chapter);
}
break;
}
case "title":
novel.putString("name", readText(parser));
break;
case "creator":
novel.putString("author", readText(parser));
break;
case "contributor":
novel.putString("artist", readText(parser));
break;
case "description":
novel.putString("summary", readText(parser));
break;
case "meta":
String metaName = parser.getAttributeValue(null, "name");
if(metaName != null && metaName.equals("cover")){
cover = parser.getAttributeValue(null, "content");
}
break;
}
parser.next();
}
}
if(cover != null){
String coverPath = contentDir + "/" + refMap.get(cover);
novel.putString("cover", coverPath);
}
novel.putArray("chapters", chapters);
return novel;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.rajarsheechatterjee.EpubUtil;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.rajarsheechatterjee.ZipArchive.ZipArchive;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class EpubUtilPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactApplicationContext) {
List<NativeModule> modules = new ArrayList<>();
try {
modules.add(new EpubUtil(reactApplicationContext));
} catch (Exception e) {
throw new RuntimeException(e);
}
return modules;
}

@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactApplicationContext) {
return Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;

import com.rajarsheechatterjee.EpubUtil.EpubUtilPackage;
import com.rajarsheechatterjee.NavigationBarColor.NavigationBarColorPackage;
import com.rajarsheechatterjee.TextFile.TextFilePackage;
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListenerPackage;
Expand All @@ -38,6 +39,7 @@ protected List<ReactPackage> getPackages() {
packages.add(new VolumeButtonListenerPackage());
packages.add(new ZipArchivePackage());
packages.add(new TextFilePackage());
packages.add(new EpubUtilPackage());
return packages;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,13 @@ public class NavigationBarColorModule extends ReactContextBaseJavaModule {
public static final String REACT_CLASS = "NavigationBarColor";
private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to change the navigation bar while not attached to an Activity";
private static final String ERROR_API_LEVEL = "API_LEVEl";
private static final String ERROR_API_LEVEL_MESSAGE = "Only Android Oreo and above is supported";
private static ReactApplicationContext reactContext = null;
private static final int UI_FLAG_HIDE_NAV_BAR = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;

public NavigationBarColorModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}

public void setNavigationBarTheme(Activity activity, Boolean light) {
Expand Down Expand Up @@ -66,61 +62,56 @@ public Map<String, Object> getConstants() {
@ReactMethod
public void changeNavigationBarColor(final String color, final Boolean light, final Boolean animated, final Promise promise) {
final WritableMap map = Arguments.createMap();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (getCurrentActivity() != null) {
try {
final Window window = getCurrentActivity().getWindow();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (color.equals("transparent") || color.equals("translucent")) {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
if (color.equals("transparent")) {
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
} else {
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
setNavigationBarTheme(getCurrentActivity(), light);
map.putBoolean("success", true);
promise.resolve(map);
return;
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
if (animated) {
Integer colorFrom = window.getNavigationBarColor();
Integer colorTo = Color.parseColor(String.valueOf(color));
//window.setNavigationBarColor(colorTo);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
window.setNavigationBarColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.start();
if (getCurrentActivity() != null) {
try {
final Window window = getCurrentActivity().getWindow();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (color.equals("transparent") || color.equals("translucent")) {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
if (color.equals("transparent")) {
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
} else {
window.setNavigationBarColor(Color.parseColor(String.valueOf(color)));
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
setNavigationBarTheme(getCurrentActivity(), light);
WritableMap map = Arguments.createMap();
map.putBoolean("success", true);
promise.resolve(map);
return;
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
});
} catch (IllegalViewOperationException e) {
map.putBoolean("success", false);
promise.reject("error", e);
}

} else {
promise.reject(ERROR_NO_ACTIVITY, new Throwable(ERROR_NO_ACTIVITY_MESSAGE));

if (animated) {
Integer colorFrom = window.getNavigationBarColor();
Integer colorTo = Color.parseColor(color);
//window.setNavigationBarColor(colorTo);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
window.setNavigationBarColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.start();
} else {
window.setNavigationBarColor(Color.parseColor(color));
}
setNavigationBarTheme(getCurrentActivity(), light);
WritableMap map = Arguments.createMap();
map.putBoolean("success", true);
promise.resolve(map);
}
});
} catch (IllegalViewOperationException e) {
map.putBoolean("success", false);
promise.reject("error", e);
}

} else {
promise.reject(ERROR_API_LEVEL, new Throwable(ERROR_API_LEVEL_MESSAGE));
promise.reject(ERROR_NO_ACTIVITY, new Throwable(ERROR_NO_ACTIVITY_MESSAGE));
}
}

Expand Down
8 changes: 6 additions & 2 deletions src/hooks/persisted/useNovel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,11 @@ export const useNovel = (novelPath: string, pluginId: string) => {
} else {
pages = (await getCustomPages(novel.id)).map(c => c.page);
}
setPages(pages);
if (pages.length) {
setPages(pages);
} else {
setPages(['1']);
}
setNovel(novel);
}, []);

Expand Down Expand Up @@ -401,8 +405,8 @@ export const useNovel = (novelPath: string, pluginId: string) => {
);
}
setChapters(chapters);
setLoading(false);
}
setLoading(false);
}, [novel, novelSettings, pageIndex]);
useEffect(() => {
getNovel();
Expand Down
8 changes: 8 additions & 0 deletions src/native/EpubUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SourceNovel } from '@plugins/types';
import { NativeModules } from 'react-native';
interface EpubUtilInterface {
parseNovelAndChapters: (epubDirPath: string) => Promise<SourceNovel>;
}
const { EpubUtil } = NativeModules;

export default EpubUtil as EpubUtilInterface;
Loading

0 comments on commit 817e4dc

Please sign in to comment.