2 * This file is part of NixNote
\r
3 * Copyright 2009 Randy Baumgarte
\r
5 * This file may be licensed under the terms of of the
\r
6 * GNU General Public License Version 2 (the ``GPL'').
\r
8 * Software distributed under the License is distributed
\r
9 * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
\r
10 * express or implied. See the GPL for the specific language
\r
11 * governing rights and limitations.
\r
13 * You should have received a copy of the GPL along with this
\r
14 * program. If not, go to http://www.gnu.org/licenses/gpl.html
\r
15 * or write to the Free Software Foundation, Inc.,
\r
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
\r
20 package cx.fbn.nevernote.threads;
\r
22 import java.util.ArrayList;
\r
23 import java.util.List;
\r
24 import java.util.concurrent.LinkedBlockingQueue;
\r
26 import com.evernote.edam.type.Note;
\r
27 import com.trolltech.qt.core.QBuffer;
\r
28 import com.trolltech.qt.core.QByteArray;
\r
29 import com.trolltech.qt.core.QIODevice;
\r
30 import com.trolltech.qt.core.QMutex;
\r
31 import com.trolltech.qt.core.QObject;
\r
32 import com.trolltech.qt.core.QTemporaryFile;
\r
33 import com.trolltech.qt.gui.QPixmap;
\r
35 import cx.fbn.nevernote.Global;
\r
36 import cx.fbn.nevernote.signals.NoteSignal;
\r
37 import cx.fbn.nevernote.sql.DatabaseConnection;
\r
38 import cx.fbn.nevernote.utilities.ApplicationLogger;
\r
39 import cx.fbn.nevernote.xml.NoteFormatter;
\r
44 * @author Randy Baumgarte
\r
46 * Thumbnail Overview:
\r
48 * How thumbnails are generated is a bit odd. The problem is that
\r
49 * process of creating the thumbnail involves actually creating an HTML
\r
50 * version of the note & all of its resources. That is very CPU intensive
\r
51 * so we try to do it in a separate thread. Unfortunately, the QWebPage class
\r
52 * which actually creates the thumbnail must be in the main GUI thread.
\r
53 * This is the odd way I've tried to get around the problem.
\r
55 * First, the thumbail thread finds a note which needs a thumbnail. This
\r
56 * can be done by either scanning the database or specifically being told
\r
57 * a note needs a new thumbnail.
\r
59 * When a note is found, this thread will read the database and write out
\r
60 * the resources and create an HTML version of the note. It then signals
\r
61 * the main GUI thread that a note is ready.
\r
63 * Next, the main GUI thread will process the signal received from the
\r
64 * thumbnail thread. The GUI thread will create a QWebPage (via the
\r
65 * Thumbnailer class) and will render the image. The image is written to
\r
66 * the database to be used in the thumbnail view.
\r
69 public class ThumbnailRunner extends QObject implements Runnable {
\r
71 private final ApplicationLogger logger;
\r
72 private String guid;
\r
73 public NoteSignal noteSignal;
\r
74 private boolean keepRunning;
\r
75 public boolean interrupt;
\r
76 private final DatabaseConnection conn;
\r
77 private volatile LinkedBlockingQueue<String> workQueue;
\r
78 private static int MAX_QUEUED_WAITING = 1000;
\r
79 public QMutex mutex;
\r
83 public ThumbnailRunner(String logname, String u, String i, String r, String uid, String pswd, String cpswd) {
\r
84 logger = new ApplicationLogger(logname);
\r
85 conn = new DatabaseConnection(logger, u, i, r, uid, pswd, cpswd, 300);
\r
86 noteSignal = new NoteSignal();
\r
89 mutex = new QMutex();
\r
90 workQueue=new LinkedBlockingQueue<String>(MAX_QUEUED_WAITING);
\r
96 thread().setPriority(Thread.MIN_PRIORITY);
\r
98 logger.log(logger.MEDIUM, "Starting thumbnail thread ");
\r
99 while (keepRunning) {
\r
102 String work = workQueue.take();
\r
103 if (work.startsWith("GENERATE")) {
\r
104 work = work.replace("GENERATE ", "");
\r
106 generateThumbnail();
\r
108 if (work.startsWith("SCAN")) {
\r
109 if (conn.getNoteTable().getThumbnailNeededCount() > 1)
\r
112 if (work.startsWith("IMAGE")) {
\r
113 work = work.replace("IMAGE ", "");
\r
117 if (work.startsWith("STOP")) {
\r
118 logger.log(logger.MEDIUM, "Stopping thumbail thread");
\r
119 keepRunning = false;
\r
121 } catch (InterruptedException e) {
\r
122 // TODO Auto-generated catch block
\r
123 e.printStackTrace();
\r
130 private void processImage() {
\r
131 boolean abort = true;
\r
135 logger.log(logger.EXTREME, "Image found "+guid);
\r
137 logger.log(logger.EXTREME, "Getting image");
\r
138 QPixmap image = new QPixmap();
\r
139 if (!image.load(Global.getFileManager().getResDirPath()+"thumbnail-"+guid+".png")) {
\r
140 logger.log(logger.EXTREME, "Failure to reload image. Aborting.");
\r
146 logger.log(logger.EXTREME, "Opening buffer");
\r
147 QBuffer buffer = new QBuffer();
\r
148 if (!buffer.open(QIODevice.OpenModeFlag.WriteOnly)) {
\r
149 logger.log(logger.EXTREME, "Failure to open buffer. Aborting.");
\r
154 logger.log(logger.EXTREME, "Filling buffer");
\r
155 if (!image.save(buffer, "PNG")) {
\r
156 logger.log(logger.EXTREME, "Failure to write to buffer. Aborting.");
\r
162 logger.log(logger.EXTREME, "Updating database");
\r
163 QByteArray b = new QBuffer(buffer).buffer();
\r
164 conn.getNoteTable().setThumbnail(guid, b);
\r
165 conn.getNoteTable().setThumbnailNeeded(guid, false);
\r
171 private void scanDatabase() {
\r
172 // If there is already work in the queue, that takes priority
\r
173 logger.log(logger.HIGH, "Scanning database for notes needing thumbnail");
\r
174 if (workQueue.size() > 0)
\r
177 // Find a few records that need thumbnails
\r
178 List<String> guids = conn.getNoteTable().findThumbnailsNeeded();
\r
179 logger.log(logger.HIGH, guids.size() +" records returned");
\r
180 for (int i=0; i<guids.size() && keepRunning && !interrupt; i++) {
\r
181 guid = guids.get(i);
\r
182 logger.log(logger.HIGH, "Working on:" +guids.get(i));
\r
183 generateThumbnail();
\r
185 logger.log(logger.HIGH, "Scan completed");
\r
189 public synchronized boolean addWork(String request) {
\r
191 if (workQueue.size() == 0) {
\r
192 workQueue.offer(request);
\r
198 public synchronized int getWorkQueueSize() {
\r
199 return workQueue.size();
\r
202 private void generateThumbnail() {
\r
203 QByteArray js = new QByteArray();
\r
204 logger.log(logger.HIGH, "Starting thumbnail for " +guid);
\r
205 ArrayList<QTemporaryFile> tempFiles = new ArrayList<QTemporaryFile>();
\r
206 Note currentNote = conn.getNoteTable().getNote(guid,true,true,false,true,false);
\r
207 NoteFormatter formatter = new NoteFormatter(logger, conn, tempFiles);
\r
208 currentNote = conn.getNoteTable().getNote(guid, true, true, false, true, false);
\r
209 formatter.setNote(currentNote, true);
\r
210 formatter.setHighlight(null);
\r
211 js.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");
\r
212 js.append("<style type=\"text/css\">.en-crypt-temp { border-collapse:collapse; border-style:solid; border-color:blue; padding:0.0mm 0.0mm 0.0mm 0.0mm; }</style>");
\r
213 js.append("<style type=\"text/css\">en-hilight { background-color: rgb(255,255,0) }</style>");
\r
214 js.append("<style> img { max-width:100%; }</style>");
\r
215 js.append("<style type=\"text/css\">en-spell { text-decoration: none; border-bottom: dotted 1px #cc0000; }</style>");
\r
216 js.append("</head>");
\r
217 js.append(formatter.rebuildNoteHTML());
\r
218 js.append("</HTML>");
\r
219 js.replace("<!DOCTYPE en-note SYSTEM 'http://xml.evernote.com/pub/enml.dtd'>", "");
\r
220 js.replace("<!DOCTYPE en-note SYSTEM 'http://xml.evernote.com/pub/enml2.dtd'>", "");
\r
221 js.replace("<?xml version='1.0' encoding='UTF-8'?>", "");
\r
223 String content = currentNote.getContent();
\r
224 zoom = Global.calculateThumbnailZoom(content);
\r
225 logger.log(logger.HIGH, "Thumbnail file ready");
\r
226 noteSignal.thumbnailPageReady.emit(guid, js, zoom);
\r