Skip to content

Instantly share code, notes, and snippets.

@chrox
Created December 6, 2012 13:22
Show Gist options
  • Save chrox/4224416 to your computer and use it in GitHub Desktop.
Save chrox/4224416 to your computer and use it in GitHub Desktop.
run KPV as a kindle booklet
package com.github.chrox.kpvbooklet;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.Date;
import org.json.simple.JSONObject;
import org.json.simple.JSONArray;
//import com.amazon.kindle.booklet.BookletContext;
import com.amazon.ebook.booklet.reader.AbstractReaderBooklet;
import com.amazon.kindle.restricted.content.catalog.ContentCatalog;
import com.amazon.kindle.restricted.runtime.Framework;
import com.amazon.ebook.util.log.d;
/**
* A Booklet for starting kpdfviewer.
*
* Modified by chrox@github.
*
* @author Patric Mueller <bhaak@gmx.net>
*/
public class KPVBooklet extends AbstractReaderBooklet {
private final d logger = d.CBb("KPVBooklet");
private final String kpdfviewer = "/mnt/us/kindlepdfviewer/kpdf.sh";
private Process kpdfviewerProcess;
public KPVBooklet() {
logger.hmA("KPVBooklet");
}
public void start(URI contentURI) {
String path = contentURI.getPath();
logger.hmA("Opening " + path + " with kindlepdfviewer...");
String[] cmd = new String[] {kpdfviewer, path};
try {
kpdfviewerProcess = Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
logger.xIb(e.toString(), e);
}
Thread thread = new kpdfviewerWaitThread(path);
thread.start();
super.start(contentURI);
}
public void stop() {
logger.hmA("stop()");
// Stop kpdfviewer
if (kpdfviewerProcess != null) {
try {
killQuitProcess(kpdfviewerProcess);
} catch (Exception e) {
logger.xIb(e.toString(), e);
}
}
super.stop();
}
protected void jLc() {
}
/**
* Send a QUIT signal to a process.
*
* See http://stackoverflow.com/questions/2950338/how-can-i-kill-a-linux-process-in-java-with-sigkill-process-destroy-does-sigte#answer-2951193
*/
private void killQuitProcess(Process process)
throws InterruptedException, IOException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if (process.getClass().getName().equals("java.lang.UNIXProcess")) {
Class cl = process.getClass();
Field field = cl.getDeclaredField("pid");
field.setAccessible(true);
Object pidObject = field.get(process);
Runtime.getRuntime().exec("kill -QUIT " + pidObject).waitFor();
} else {
throw new IllegalArgumentException("Needs to be a UNIXProcess");
}
}
/** This thread waits for kpdfviewer to finish and then sends
* a BACKWARD lipc event.
*/
class kpdfviewerWaitThread extends Thread {
private String content_path = "";
public kpdfviewerWaitThread(String path) {
content_path = path;
}
public void run() {
try {
// wait for kpdfviewer to finish
kpdfviewerProcess.waitFor();
// sent BACKWARD lipc event if kpdfviewer finished normally
//Runtime.getRuntime().exec("lipc-set-prop com.lab126.appmgrd backward 0");
Runtime.getRuntime().exec("lipc-set-prop com.lab126.appmgrd start app://com.lab126.booklet.home");
} catch (IOException e) {
logger.xIb(e.toString(), e);
} catch (InterruptedException e) {
logger.xIb(e.toString(), e);
}
updateCC(content_path, 0.0f);
}
}
/**
* Update lastAccess and displayTag fields in ContentCatlog
* @param file path
*/
private void updateCC(String path, float percentFinished) {
long lastAccess = new Date().getTime() / 1000L;
int dot = path.lastIndexOf('.');
String tag = (dot == -1) ? "" : path.substring(dot+1).toUpperCase();
path = JSONObject.escape(path);
String json_query = "{\"filter\":{\"Equals\":{\"value\":\"" + path + "\",\"path\":\"location\"}},\"type\":\"QueryRequest\",\"maxResults\":1,\"sortOrder\":[{\"order\":\"descending\",\"path\":\"lastAccess\"},{\"order\":\"ascending\",\"path\":\"titles[0].collation\"}],\"startIndex\":0,\"id\":1,\"resultType\":\"fast\"}";
JSONObject json = CCRequest("query", json_query);
JSONArray values = (JSONArray) json.get("values");
JSONObject value =(JSONObject) values.get(0);
String uuid = (String) value.get("uuid");
String json_change = "{\"commands\":[{\"update\":{\"uuid\":\"" + uuid + "\",\"lastAccess\":" + lastAccess + ",\"percentFinished\":" + percentFinished + ",\"displayTags\":[\"" + tag + "\"]" + "}}],\"type\":\"ChangeRequest\",\"id\":1}";
CCRequest("change", json_change);
logger.hmA("UpdateCC:file:" + path + ",lastAccess:" + lastAccess + ",percentFinished:" + percentFinished);
}
/**
* Perform CC request of type "query" and "change"
* @param req_type request type of "query" or "change"
* @param req_json request json string
* @return return json object
*/
private JSONObject CCRequest(String req_type, String req_json) {
ContentCatalog CC = (ContentCatalog)Framework.getService(ContentCatalog.class);
JSONObject json = CC.eL(req_type, req_json, 200, 5);
return json;
}
}
@houqp
Copy link

houqp commented Dec 6, 2012

Just out of curiosity, how did you figured out the way to sync the progress between KPV and native readers? Did you reversed the native system?

@chrox
Copy link
Author

chrox commented Dec 7, 2012

Yes. A lot of reverse-engineering indeed. I just got a piece of log saying "IndexerBundle:CallCCDirect:type=change,json=" after closing a book when debug is ON. I then greped the string "IndexerBundle" in Amazon's /opt/amazon/ebook directory and I found a class named "IndexerBundle" lib/kafindexer.jar. And I decompiled the IndexerBundle.class with jad. Fortunately the IndexerBundle.eL method has the string "callCC-Direct" in the code which resembles a lot of the log. I then focused on this method. I used the patching method described in https://gist.github.com/4193209 to debug this method.

What I did was logging the calling arguments and the returning object to identify the behavior of the method.
This can be done in this way:

private String patchMethodCallCC(BCClass clazz) throws Exception {
        Code c = clazz.getDeclaredMethod("eL").getCode(false);
        //dump(c);
        c.before(220);   // before return 
        c.aload().setLocal(1); // load the first argument
        c.aload().setLocal(2); // load the second argument
        c.aload().setLocal(11); // load the to be returned object
        c.invokestatic().setMethod(BookletPatch.class.getDeclaredMethod("ccRequestLog", new Class[] {String.class, String.class, JSONObject.class})); // call ccRequestLog with loaded arguments
        //dump(c);
        sanitize(c);
        return null;
}

public static void ccRequestLog(String type, String cal_json, JSONObject ret_json) {
        log("CCRequest:type:" + type +",json=" + cal_json);
        log(".....................................");
        log("CCReturn:json=" + ret_json);
        /*try {
            throw new RuntimeException();
        } catch (Exception e) {
            e.printStackTrace(log);
        }*/
}

From the log we can read the detailed json query and change format. I found that synchronizing the progress requires document's uuid which can be obtained from a json query with the file path. I figured out how to invoke this eL method by display the StackTrace of each eL call(just comment out the try...catch code in ccRequestLog). It showed that the com.amazon.indexer.b.g.jOb method calls the eL method. And it calls like this way:

ContentCatalog CC = (ContentCatalog)Framework.getService(ContentCatalog.class);
JSONObject json = CC.eL(req_type, req_json, 200, 5);

That's the whole reverse work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment