/*\r
- * This file is part of NeverNote \r
+ * This file is part of NixNote \r
* Copyright 2009 Randy Baumgarte\r
* \r
* This file may be licensed under the terms of of the\r
import java.util.concurrent.LinkedBlockingQueue;\r
\r
import com.evernote.edam.type.Note;\r
+import com.trolltech.qt.core.QBuffer;\r
import com.trolltech.qt.core.QByteArray;\r
-import com.trolltech.qt.core.QFile;\r
-import com.trolltech.qt.core.QIODevice.OpenModeFlag;\r
+import com.trolltech.qt.core.QIODevice;\r
+import com.trolltech.qt.core.QMutex;\r
import com.trolltech.qt.core.QObject;\r
import com.trolltech.qt.core.QTemporaryFile;\r
+import com.trolltech.qt.gui.QPixmap;\r
\r
import cx.fbn.nevernote.Global;\r
import cx.fbn.nevernote.signals.NoteSignal;\r
import cx.fbn.nevernote.utilities.ApplicationLogger;\r
import cx.fbn.nevernote.xml.NoteFormatter;\r
\r
+\r
+/*\r
+ * \r
+ * @author Randy Baumgarte\r
+ * \r
+ * Thumbnail Overview:\r
+ * \r
+ * How thumbnails are generated is a bit odd. The problem is that \r
+ * process of creating the thumbnail involves actually creating an HTML\r
+ * version of the note & all of its resources. That is very CPU intensive\r
+ * so we try to do it in a separate thread. Unfortunately, the QWebPage class \r
+ * which actually creates the thumbnail must be in the main GUI thread.\r
+ * This is the odd way I've tried to get around the problem.\r
+ * \r
+ * First, the thumbail thread finds a note which needs a thumbnail. This\r
+ * can be done by either scanning the database or specifically being told\r
+ * a note needs a new thumbnail. \r
+ * \r
+ * When a note is found, this thread will read the database and write out\r
+ * the resources and create an HTML version of the note. It then signals\r
+ * the main GUI thread that a note is ready. \r
+ * \r
+ * Next, the main GUI thread will process the signal received from the \r
+ * thumbnail thread. The GUI thread will create a QWebPage (via the\r
+ * Thumbnailer class) and will render the image. The image is written to \r
+ * the database to be used in the thumbnail view.\r
+ * \r
+ */\r
public class ThumbnailRunner extends QObject implements Runnable {\r
\r
private final ApplicationLogger logger;\r
private String guid;\r
public NoteSignal noteSignal;\r
private boolean keepRunning;\r
+ public boolean interrupt;\r
private final DatabaseConnection conn;\r
private volatile LinkedBlockingQueue<String> workQueue;\r
private static int MAX_QUEUED_WAITING = 1000;\r
+ public QMutex mutex;\r
\r
\r
\r
- public ThumbnailRunner(String logname, String u, String uid, String pswd, String cpswd) {\r
+ public ThumbnailRunner(String logname, String u, String i, String r, String uid, String pswd, String cpswd) {\r
logger = new ApplicationLogger(logname);\r
- conn = new DatabaseConnection(logger, u, uid, pswd, cpswd);\r
+ conn = new DatabaseConnection(logger, u, i, r, uid, pswd, cpswd, 300);\r
noteSignal = new NoteSignal();\r
guid = null;\r
keepRunning = true;\r
+ mutex = new QMutex();\r
workQueue=new LinkedBlockingQueue<String>(MAX_QUEUED_WAITING); \r
}\r
\r
logger.log(logger.MEDIUM, "Starting thumbnail thread ");\r
while (keepRunning) {\r
try {\r
+ interrupt = false;\r
String work = workQueue.take();\r
if (work.startsWith("GENERATE")) {\r
work = work.replace("GENERATE ", "");\r
generateThumbnail();\r
}\r
if (work.startsWith("SCAN")) {\r
- scanDatabase();\r
+ if (conn.getNoteTable().getThumbnailNeededCount() > 1)\r
+ scanDatabase();\r
+ }\r
+ if (work.startsWith("IMAGE")) {\r
+ work = work.replace("IMAGE ", "");\r
+ guid = work;\r
+ processImage();\r
}\r
if (work.startsWith("STOP")) {\r
logger.log(logger.MEDIUM, "Stopping thumbail thread");\r
}\r
\r
\r
+ private void processImage() {\r
+ boolean abort = true;\r
+ if (abort)\r
+ return;\r
+ mutex.lock();\r
+ logger.log(logger.EXTREME, "Image found "+guid);\r
+ \r
+ logger.log(logger.EXTREME, "Getting image");\r
+ QPixmap image = new QPixmap();\r
+ if (!image.load(Global.getFileManager().getResDirPath()+"thumbnail-"+guid+".png")) {\r
+ logger.log(logger.EXTREME, "Failure to reload image. Aborting.");\r
+ mutex.unlock();\r
+ return;\r
+ }\r
+ \r
+ \r
+ logger.log(logger.EXTREME, "Opening buffer");\r
+ QBuffer buffer = new QBuffer();\r
+ if (!buffer.open(QIODevice.OpenModeFlag.WriteOnly)) {\r
+ logger.log(logger.EXTREME, "Failure to open buffer. Aborting.");\r
+ mutex.unlock();\r
+ return;\r
+ }\r
+ \r
+ logger.log(logger.EXTREME, "Filling buffer");\r
+ if (!image.save(buffer, "PNG")) {\r
+ logger.log(logger.EXTREME, "Failure to write to buffer. Aborting."); \r
+ mutex.unlock();\r
+ return;\r
+ }\r
+ buffer.close();\r
+ \r
+ logger.log(logger.EXTREME, "Updating database");\r
+ QByteArray b = new QBuffer(buffer).buffer();\r
+ conn.getNoteTable().setThumbnail(guid, b);\r
+ conn.getNoteTable().setThumbnailNeeded(guid, false);\r
+ mutex.unlock();\r
+ }\r
+ \r
+ \r
+ \r
private void scanDatabase() {\r
// If there is already work in the queue, that takes priority\r
logger.log(logger.HIGH, "Scanning database for notes needing thumbnail");\r
// Find a few records that need thumbnails\r
List<String> guids = conn.getNoteTable().findThumbnailsNeeded();\r
logger.log(logger.HIGH, guids.size() +" records returned");\r
- for (int i=0; i<guids.size() && keepRunning; i++) {\r
+ for (int i=0; i<guids.size() && keepRunning && !interrupt; i++) {\r
guid = guids.get(i);\r
logger.log(logger.HIGH, "Working on:" +guids.get(i));\r
generateThumbnail();\r
\r
\r
public synchronized boolean addWork(String request) {\r
+\r
if (workQueue.size() == 0) {\r
workQueue.offer(request);\r
return true;\r
js.replace("<!DOCTYPE en-note SYSTEM 'http://xml.evernote.com/pub/enml.dtd'>", "");\r
js.replace("<!DOCTYPE en-note SYSTEM 'http://xml.evernote.com/pub/enml2.dtd'>", "");\r
js.replace("<?xml version='1.0' encoding='UTF-8'?>", "");\r
- String fileName = Global.getFileManager().getResDirPath("thumbnail-" + guid + ".html");\r
- QFile tFile = new QFile(fileName);\r
- tFile.open(OpenModeFlag.WriteOnly);\r
- tFile.write(js);\r
- tFile.close();\r
+ int zoom = 1;\r
+ if (currentNote != null && currentNote.getContent() != null) {\r
+ String content = currentNote.getContent();\r
+ zoom = Global.calculateThumbnailZoom(content);\r
+ }\r
logger.log(logger.HIGH, "Thumbnail file ready");\r
- noteSignal.thumbnailPageReady.emit(guid, fileName);\r
+ noteSignal.thumbnailPageReady.emit(guid, js, zoom);\r
}\r
\r
\r