feat: Import EPUB (#833)
* implement classes for plugins * feat: plugin manages UA, cookie itself * fix: repo path * safer regexString * feat: EpubParser native module * add reject to parser * local category, better insert category trigger * add isLocal field to Novel * category update logic for local novels * feat: import epub * delete downloaded folder when delete novel * local novel screen * update build.yml * fix: novel without cover
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package com.rajarsheechatterjee.EpubParser;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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.ReadableArray;
|
||||
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.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
public class EpubParser extends ReactContextBaseJavaModule {
|
||||
String rootPath;
|
||||
String contentDirPath;
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
|
||||
EpubParser(ReactApplicationContext context) throws Exception {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "EpubParser";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void parse(String epubFilePath, String epubDirPath, Promise promise) {
|
||||
try{
|
||||
this.rootPath = epubDirPath;
|
||||
String novelCover = this.unzip(epubFilePath, epubDirPath);
|
||||
File contentFile = this.parseContainer(epubDirPath);
|
||||
this.contentDirPath = contentFile.getParent();
|
||||
WritableMap novel = (WritableMap) this.parseContent(contentFile);
|
||||
if(novelCover != null){
|
||||
novel.putString("cover", new File(epubDirPath, novelCover).getAbsolutePath());
|
||||
}
|
||||
promise.resolve(novel);
|
||||
} catch (Exception e) {
|
||||
promise.reject(e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
public File parseContainer(String epubDir) throws Exception {
|
||||
Document doc = this.parserDocument(new File(epubDir, "META-INF/container.xml"));
|
||||
return new File(this.rootPath,
|
||||
((Element) doc.getElementsByTagName("rootfile").item(0)).getAttribute("full-path"));
|
||||
}
|
||||
|
||||
public ReadableMap parseContent(File contentFile) throws Exception {
|
||||
Document content = this.parserDocument(contentFile);
|
||||
WritableMap novel = new WritableNativeMap();
|
||||
novel.putString("name", this.getFirstElementByTag(content, "dc:title").getTextContent().trim());
|
||||
novel.putString("url", contentFile.getParent());
|
||||
novel.putString("author", this.getFirstElementByTag(content, "dc:creator").getTextContent().trim());
|
||||
novel.putArray("chapters", this.getChapters(content));
|
||||
return novel;
|
||||
}
|
||||
|
||||
public ReadableArray getChapters(Document doc) throws Exception {
|
||||
WritableArray chapters = new WritableNativeArray();
|
||||
Document toc = null;
|
||||
if (this.getFirstElementByTag(doc, "spine").getAttribute("toc") != null) {
|
||||
toc = this.parserDocument(new File(this.contentDirPath, "toc.ncx"));
|
||||
}
|
||||
NodeList itemrefs = this.getNodeListByTag(doc, "itemref");
|
||||
for (int i = 0; i < itemrefs.getLength(); i++) {
|
||||
WritableMap chapter = new WritableNativeMap();
|
||||
Element itemref = (Element) itemrefs.item(i);
|
||||
chapter.putString("name", itemref.getAttribute("idref"));
|
||||
Element item = this.getElementById(doc, chapter.getString("name"));
|
||||
chapter.putString("url", new File(this.contentDirPath, item.getAttribute("href")).getAbsolutePath());
|
||||
if(toc != null){
|
||||
chapter.putString("name", this.getChapterName(toc, item.getAttribute(("href"))));
|
||||
if(chapter.getString("name") == null) continue;
|
||||
}
|
||||
chapters.pushMap(chapter);
|
||||
}
|
||||
return chapters;
|
||||
}
|
||||
|
||||
public String getChapterName(Document doc, String href) throws Exception {
|
||||
String path = String.format("//*[starts-with(@src,'%1$s')]", href);
|
||||
Node node = ((NodeList) this.xPath.compile(path).evaluate(doc, XPathConstants.NODESET)).item(0);
|
||||
if(node != null){
|
||||
String text = node.getPreviousSibling().getTextContent().trim();
|
||||
if(text.isEmpty()) text = node.getPreviousSibling().getPreviousSibling().getTextContent().trim();
|
||||
return !text.isEmpty() ? text : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Element getElementById(Document doc, String id) throws Exception {
|
||||
return (Element) ((NodeList) this.xPath
|
||||
.compile(String.format("//*[@id='%1$s']", id))
|
||||
.evaluate(doc, XPathConstants.NODESET))
|
||||
.item(0);
|
||||
}
|
||||
|
||||
public NodeList getNodeListByTag(Document doc, String tag) throws Exception {
|
||||
return (NodeList)
|
||||
(
|
||||
this.xPath.compile(String.format("//*[contains(name(), '%1$s')]", tag))
|
||||
.evaluate(doc, XPathConstants.NODESET)
|
||||
);
|
||||
}
|
||||
|
||||
public Element getFirstElementByTag(Document doc, String tag) throws Exception {
|
||||
return (Element) this.getNodeListByTag(doc, tag).item(0);
|
||||
}
|
||||
|
||||
public Document parserDocument(File docFile) throws Exception {
|
||||
Document doc = this.db.parse(docFile);
|
||||
doc.getDocumentElement().normalize();
|
||||
return doc;
|
||||
}
|
||||
|
||||
public String unzip(String epubFilePath, String epubDirPath) throws Exception {
|
||||
ZipInputStream zis = new ZipInputStream(new FileInputStream(epubFilePath));
|
||||
ZipEntry zipEntry;
|
||||
int len;
|
||||
String novelCover = null;
|
||||
byte[] buffer = new byte[4096];
|
||||
while ((zipEntry = zis.getNextEntry()) != null) {
|
||||
if(Pattern.compile(".*cover.*\\.(png|jpeg|jpg)", Pattern.CASE_INSENSITIVE).matcher(zipEntry.getName()).matches()){
|
||||
novelCover = zipEntry.getName();
|
||||
}
|
||||
File newFile = new File(epubDirPath, zipEntry.getName());
|
||||
newFile.getParentFile().mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(newFile);
|
||||
while ((len = zis.read(buffer)) > 0)
|
||||
fos.write(buffer, 0, len);
|
||||
fos.close();
|
||||
}
|
||||
zis.closeEntry();
|
||||
zis.close();
|
||||
return novelCover;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.rajarsheechatterjee.EpubParser;
|
||||
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class EpubParserPackage implements ReactPackage {
|
||||
@NonNull
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactApplicationContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
try {
|
||||
modules.add(new EpubParser(reactApplicationContext));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactApplicationContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListener;
|
||||
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
||||
|
||||
import org.devio.rn.splashscreen.SplashScreen;
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.rajarsheechatterjee.NavigationBarColor.NavigationBarColorPackage;
|
||||
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListenerPackage;
|
||||
import com.rajarsheechatterjee.EpubParser.EpubParserPackage;
|
||||
|
||||
import expo.modules.ApplicationLifecycleDispatcher;
|
||||
import expo.modules.ReactNativeHostWrapper;
|
||||
@@ -21,10 +23,6 @@ import com.swmansion.reanimated.ReanimatedJSIModulePackage;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
import org.devio.rn.splashscreen.SplashScreenReactPackage;
|
||||
import com.rnfs.RNFSPackage;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
private final ReactNativeHost mReactNativeHost =
|
||||
@@ -36,10 +34,10 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
List<ReactPackage> packages = new PackageList(this).getPackages();
|
||||
packages.add(new com.rajarsheechatterjee.LNReader.NavigationBarColorPackage());
|
||||
packages.add(new NavigationBarColorPackage());
|
||||
packages.add(new VolumeButtonListenerPackage());
|
||||
packages.add(new EpubParserPackage());
|
||||
return packages;
|
||||
}
|
||||
|
||||
@@ -69,14 +67,6 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
ApplicationLifecycleDispatcher.onApplicationCreate(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Flipper in React Native templates. Call this in the onCreate method
|
||||
* with something like
|
||||
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||
*
|
||||
* @param context
|
||||
* @param reactInstanceManager
|
||||
*/
|
||||
private static void initializeFlipper(
|
||||
Context context, ReactInstanceManager reactInstanceManager) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
||||
+1
-4
@@ -1,21 +1,18 @@
|
||||
package com.rajarsheechatterjee.LNReader;
|
||||
package com.rajarsheechatterjee.NavigationBarColor;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import androidx.annotation.UiThread;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
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.UiThreadUtil;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.rajarsheechatterjee.LNReader;
|
||||
package com.rajarsheechatterjee.NavigationBarColor;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
|
||||
Reference in New Issue
Block a user