OSDN Git Service

Correct zoom label not hiding properly.
[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         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
80 \r
81 \r
82 \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
87                 guid = null;\r
88                 keepRunning = true;\r
89                 mutex = new QMutex();\r
90                 workQueue=new LinkedBlockingQueue<String>(MAX_QUEUED_WAITING);  \r
91         }\r
92         \r
93         \r
94         @Override\r
95         public void run() {\r
96                 thread().setPriority(Thread.MIN_PRIORITY);\r
97                 \r
98                 logger.log(logger.MEDIUM, "Starting thumbnail thread ");\r
99                 while (keepRunning) {\r
100                         try {\r
101                                 interrupt = false;\r
102                                 String work = workQueue.take();\r
103                                 if (work.startsWith("GENERATE")) {\r
104                                         work = work.replace("GENERATE ", "");\r
105                                         guid = work;\r
106                                         generateThumbnail();\r
107                                 }\r
108                                 if (work.startsWith("SCAN")) {\r
109                                         if (conn.getNoteTable().getThumbnailNeededCount() > 1)\r
110                                                 scanDatabase();\r
111                                 }\r
112                                 if (work.startsWith("IMAGE")) {\r
113                                         work = work.replace("IMAGE ", "");\r
114                                         guid = work;\r
115                                         processImage();\r
116                                 }\r
117                                 if (work.startsWith("STOP")) {\r
118                                         logger.log(logger.MEDIUM, "Stopping thumbail thread");\r
119                                         keepRunning = false;\r
120                                 }\r
121                         } catch (InterruptedException e) {\r
122                                 // TODO Auto-generated catch block\r
123                                 e.printStackTrace();\r
124                         }\r
125                 }\r
126                 conn.dbShutdown();\r
127         }\r
128         \r
129         \r
130         private void processImage() {\r
131                 boolean abort = true;\r
132                 if (abort)\r
133                         return;\r
134                 mutex.lock();\r
135                 logger.log(logger.EXTREME, "Image found "+guid);\r
136                         \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
141                         mutex.unlock();\r
142                         return;\r
143                 }\r
144                 \r
145                 \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
150                 mutex.unlock();\r
151                 return;\r
152         }\r
153                 \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
157                 mutex.unlock();\r
158                 return;\r
159         }\r
160         buffer.close();\r
161                 \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
166                 mutex.unlock();\r
167         }\r
168         \r
169         \r
170         \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
175                         return;\r
176                 \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
184                 }\r
185                 logger.log(logger.HIGH, "Scan completed");\r
186         }\r
187 \r
188                 \r
189         public synchronized boolean addWork(String request) {\r
190 \r
191                 if (workQueue.size() == 0) {\r
192                         workQueue.offer(request);\r
193                         return true;\r
194                 }\r
195                 return false;\r
196         }\r
197         \r
198         public synchronized int getWorkQueueSize() {\r
199                 return workQueue.size();\r
200         }\r
201         \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
222                 int zoom = 1;\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
227         }\r
228                 \r
229         \r
230 \r
231 \r
232 }\r