OSDN Git Service

024ce6f5019ba131b521f21417e3a5b06fda089b
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / threads / ThumbnailRunner.java
1 /*\r
2  * This file is part of NeverNote \r
3  * Copyright 2009 Randy Baumgarte\r
4  * \r
5  * This file may be licensed under the terms of of the\r
6  * GNU General Public License Version 2 (the ``GPL'').\r
7  *\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
12  *\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
17  *\r
18 */\r
19 \r
20 package cx.fbn.nevernote.threads;\r
21 \r
22 import java.util.ArrayList;\r
23 import java.util.List;\r
24 import java.util.concurrent.LinkedBlockingQueue;\r
25 \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
34 \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
40 \r
41 \r
42 /*\r
43  * \r
44  * @author Randy Baumgarte\r
45  * \r
46  * Thumbnail Overview:\r
47  * \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
54  * \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
58  * \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
62  * \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
67  * \r
68  */\r
69 public class ThumbnailRunner extends QObject implements Runnable {\r
70         \r
71         private final ApplicationLogger                         logger;\r
72         private String                                                          guid;\r
73         public  NoteSignal                                                      noteSignal;\r
74         private boolean                                                         keepRunning;\r
75         private final DatabaseConnection                        conn;\r
76         private volatile LinkedBlockingQueue<String> workQueue;\r
77         private static int                                                      MAX_QUEUED_WAITING = 1000;\r
78         public QMutex                                                           mutex;\r
79 \r
80 \r
81 \r
82         public ThumbnailRunner(String logname, String u, String uid, String pswd, String cpswd) {\r
83                 logger = new ApplicationLogger(logname);\r
84                 conn = new DatabaseConnection(logger, u, uid, pswd, cpswd, 300);\r
85                 noteSignal = new NoteSignal();\r
86                 guid = null;\r
87                 keepRunning = true;\r
88                 mutex = new QMutex();\r
89                 workQueue=new LinkedBlockingQueue<String>(MAX_QUEUED_WAITING);  \r
90         }\r
91         \r
92         \r
93         @Override\r
94         public void run() {\r
95                 thread().setPriority(Thread.MIN_PRIORITY);\r
96                 \r
97                 logger.log(logger.MEDIUM, "Starting thumbnail thread ");\r
98                 while (keepRunning) {\r
99                         try {\r
100                                 String work = workQueue.take();\r
101                                 if (work.startsWith("GENERATE")) {\r
102                                         work = work.replace("GENERATE ", "");\r
103                                         guid = work;\r
104                                         generateThumbnail();\r
105                                 }\r
106                                 if (work.startsWith("SCAN")) {\r
107                                         scanDatabase();\r
108                                 }\r
109                                 if (work.startsWith("IMAGE")) {\r
110                                         work = work.replace("IMAGE ", "");\r
111                                         guid = work;\r
112                                         processImage();\r
113                                 }\r
114                                 if (work.startsWith("STOP")) {\r
115                                         logger.log(logger.MEDIUM, "Stopping thumbail thread");\r
116                                         keepRunning = false;\r
117                                 }\r
118                         } catch (InterruptedException e) {\r
119                                 // TODO Auto-generated catch block\r
120                                 e.printStackTrace();\r
121                         }\r
122                 }\r
123                 conn.dbShutdown();\r
124         }\r
125         \r
126         \r
127         private void processImage() {\r
128                 boolean abort = true;\r
129                 if (abort)\r
130                         return;\r
131                 mutex.lock();\r
132                 logger.log(logger.EXTREME, "Image found "+guid);\r
133                         \r
134                 logger.log(logger.EXTREME, "Getting image");\r
135                 QPixmap image = new QPixmap();\r
136                 if (!image.load(Global.getFileManager().getResDirPath()+"thumbnail-"+guid+".png")) {\r
137                         logger.log(logger.EXTREME, "Failure to reload image. Aborting.");\r
138                         mutex.unlock();\r
139                         return;\r
140                 }\r
141                 \r
142                 \r
143                 logger.log(logger.EXTREME, "Opening buffer");\r
144         QBuffer buffer = new QBuffer();\r
145         if (!buffer.open(QIODevice.OpenModeFlag.WriteOnly)) {\r
146                 logger.log(logger.EXTREME, "Failure to open buffer.  Aborting.");\r
147                 mutex.unlock();\r
148                 return;\r
149         }\r
150                 \r
151                 logger.log(logger.EXTREME, "Filling buffer");\r
152         if (!image.save(buffer, "PNG")) {\r
153                 logger.log(logger.EXTREME, "Failure to write to buffer.  Aborting.");     \r
154                 mutex.unlock();\r
155                 return;\r
156         }\r
157         buffer.close();\r
158                 \r
159                 logger.log(logger.EXTREME, "Updating database");\r
160                 QByteArray b = new QBuffer(buffer).buffer();\r
161                 conn.getNoteTable().setThumbnail(guid, b);\r
162                 conn.getNoteTable().setThumbnailNeeded(guid, false);\r
163                 mutex.unlock();\r
164         }\r
165         \r
166         \r
167         \r
168         private void scanDatabase() {\r
169                 // If there is already work in the queue, that takes priority\r
170                 logger.log(logger.HIGH, "Scanning database for notes needing thumbnail");\r
171                 if (workQueue.size() > 0)\r
172                         return;\r
173                 \r
174                 // Find a few records that need thumbnails\r
175                 List<String> guids = conn.getNoteTable().findThumbnailsNeeded();\r
176                 logger.log(logger.HIGH, guids.size() +" records returned");\r
177                 for (int i=0; i<guids.size() && keepRunning; i++) {\r
178                         guid = guids.get(i);\r
179                         logger.log(logger.HIGH, "Working on:" +guids.get(i));\r
180                         generateThumbnail();\r
181                 }\r
182                 logger.log(logger.HIGH, "Scan completed");\r
183         }\r
184 \r
185                 \r
186         public synchronized boolean addWork(String request) {\r
187 \r
188                 if (workQueue.size() == 0) {\r
189                         workQueue.offer(request);\r
190                         return true;\r
191                 }\r
192                 return false;\r
193         }\r
194         \r
195         public synchronized int getWorkQueueSize() {\r
196                 return workQueue.size();\r
197         }\r
198         \r
199         private void generateThumbnail() {\r
200                 QByteArray js = new QByteArray();\r
201                 logger.log(logger.HIGH, "Starting thumbnail for " +guid);\r
202                 ArrayList<QTemporaryFile> tempFiles = new ArrayList<QTemporaryFile>();\r
203                 Note currentNote = conn.getNoteTable().getNote(guid,true,true,false,true,false);\r
204                 NoteFormatter formatter = new NoteFormatter(logger, conn, tempFiles);\r
205                 currentNote = conn.getNoteTable().getNote(guid, true, true, false, true, false);\r
206                 formatter.setNote(currentNote, true);\r
207                 formatter.setHighlight(null);\r
208                 js.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");               \r
209                 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
210                 js.append("<style type=\"text/css\">en-hilight { background-color: rgb(255,255,0) }</style>");\r
211                 js.append("<style> img { max-width:100%; }</style>");\r
212                 js.append("<style type=\"text/css\">en-spell { text-decoration: none; border-bottom: dotted 1px #cc0000; }</style>");\r
213                 js.append("</head>");\r
214                 js.append(formatter.rebuildNoteHTML());\r
215                 js.append("</HTML>");\r
216                 js.replace("<!DOCTYPE en-note SYSTEM 'http://xml.evernote.com/pub/enml.dtd'>", "");\r
217                 js.replace("<!DOCTYPE en-note SYSTEM 'http://xml.evernote.com/pub/enml2.dtd'>", "");\r
218                 js.replace("<?xml version='1.0' encoding='UTF-8'?>", "");\r
219                 int zoom = 1;\r
220                 String content = currentNote.getContent();\r
221                 zoom = Global.calculateThumbnailZoom(content);\r
222                 logger.log(logger.HIGH, "Thumbnail file ready");\r
223                 noteSignal.thumbnailPageReady.emit(guid, js, zoom);\r
224         }\r
225                 \r
226         \r
227 \r
228 \r
229 }\r