1 /**************************************************************************
2 * Copyright (C) 2011-2012 by Michel Ludwig (michel.ludwig@kdemail.net) *
3 ***************************************************************************/
4
5 /***************************************************************************
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 ***************************************************************************/
13
14 #include "parserthread.h"
15
16 #include "documentinfo.h"
17 #include "kiledocmanager.h"
18 #include "kileinfo.h"
19 #include "bibtexparser.h"
20 #include "latexparser.h"
21 #include "latexoutputparser.h"
22
23 namespace KileParser {
24
DocumentParserInput(const QUrl & url,QStringList lines,ParserType parserType,const QMap<QString,KileStructData> * dictStructLevel,bool showSectioningLabels,bool showStructureTodo)25 DocumentParserInput::DocumentParserInput(const QUrl &url, QStringList lines,
26 ParserType parserType,
27 const QMap<QString, KileStructData>* dictStructLevel,
28 bool showSectioningLabels,
29 bool showStructureTodo)
30 : ParserInput(url),
31 lines(lines),
32 parserType(parserType),
33 dictStructLevel(dictStructLevel),
34 showSectioningLabels(showSectioningLabels),
35 showStructureTodo(showStructureTodo)
36 {
37 }
38
ParserThread(KileInfo * info,QObject * parent)39 ParserThread::ParserThread(KileInfo *info, QObject *parent) :
40 QThread(parent),
41 m_ki(info),
42 m_keepParserThreadAlive(true)
43 {
44 }
45
~ParserThread()46 ParserThread::~ParserThread()
47 {
48 qCDebug(LOG_KILE_PARSER) << "destroying parser thread" << this;
49 stopParsing();
50 // wait for the thread to finish before it is deleted at
51 // the end of this destructor
52 qCDebug(LOG_KILE_PARSER) << "waiting for parser thread to finish...";
53 wait();
54 // and delete remaining queue items (no mutex is required
55 // as the thread's execution has stopped)
56 qDeleteAll(m_parserQueue);
57 }
58
addParserInput(ParserInput * input)59 void ParserThread::addParserInput(ParserInput *input)
60 {
61 qCDebug(LOG_KILE_PARSER) << input;
62 qCDebug(LOG_KILE_PARSER) << "trying to obtain m_parserMutex";
63
64 m_parserMutex.lock();
65 // first, check whether the document is queued already
66 QQueue<ParserInput*>::iterator it = m_parserQueue.begin();
67 for(; it != m_parserQueue.end(); ++it) {
68 if((*it)->url == input->url) {
69 break;
70 }
71 }
72
73 if(it != m_parserQueue.end()) {
74 qCDebug(LOG_KILE_PARSER) << "document in queue already";
75 *it = input;
76 }
77 else {
78 if(m_currentlyParsedUrl == input->url) {
79 qCDebug(LOG_KILE_PARSER) << "re-parsing document";
80 // stop the parsing of the document
81 m_keepParsingDocument = false;
82 // and add it as first element to the queue
83 m_parserQueue.push_front(input);
84 }
85 else {
86 qCDebug(LOG_KILE_PARSER) << "adding to the end";
87 m_parserQueue.push_back(input);
88 }
89 }
90 m_parserMutex.unlock();
91
92 // finally, wake up threads waiting for the queue to be filled
93 m_queueEmptyWaitCondition.wakeAll();
94 }
95
removeParserInput(const QUrl & url)96 void ParserThread::removeParserInput(const QUrl &url)
97 {
98 qCDebug(LOG_KILE_PARSER) << url;
99 m_parserMutex.lock();
100 // first, if the document is currently parsed, we stop the parsing
101 if(m_currentlyParsedUrl == url) {
102 qCDebug(LOG_KILE_PARSER) << "document currently being parsed";
103 m_keepParsingDocument = false;
104 }
105 // nevertheless, we remove all traces of the document from the queue
106 for(QQueue<ParserInput*>::iterator it = m_parserQueue.begin(); it != m_parserQueue.end();) {
107 ParserInput *input = *it;
108 if(input->url == url) {
109 qCDebug(LOG_KILE_PARSER) << "found it";
110 it = m_parserQueue.erase(it);
111 delete input;
112 }
113 else {
114 ++it;
115 }
116 }
117 m_parserMutex.unlock();
118 }
119
stopParsing()120 void ParserThread::stopParsing()
121 {
122 qCDebug(LOG_KILE_PARSER);
123 m_parserMutex.lock();
124
125 m_keepParserThreadAlive = false;
126 m_keepParsingDocument = false;
127 m_parserMutex.unlock();
128 // wake all the threads that are still waiting for the queue to fill up
129 m_queueEmptyWaitCondition.wakeAll();
130 }
131
shouldContinueDocumentParsing()132 bool ParserThread::shouldContinueDocumentParsing()
133 {
134 QMutexLocker locker(&m_parserMutex);
135 return m_keepParsingDocument;
136 }
137
isParsingComplete()138 bool ParserThread::isParsingComplete()
139 {
140 QMutexLocker locker(&m_parserMutex);
141 // as the parser queue might be empty but a document is still being parsed,
142 // we additionally have to check whether 'm_currentlyParsedUrl' is empty or not
143 return m_parserQueue.isEmpty() && m_currentlyParsedUrl.isEmpty();
144 }
145
146 // the document that is currently parsed is always the head of the queue
run()147 void ParserThread::run()
148 {
149 ParserInput* currentParsedItem;
150 qCDebug(LOG_KILE_PARSER) << "starting up...";
151 while(true) {
152 // first, try to extract the head of the queue
153 m_parserMutex.lock();
154 // clear the variable currently parsed url; might be necessary from the previous iteration
155 m_currentlyParsedUrl = QUrl();
156 // check if we should still be running before going to sleep
157 if(!m_keepParserThreadAlive) {
158 m_parserMutex.unlock();
159 // remaining queue elements are deleted in the destructor
160 return;
161 }
162 // but if there are no items to be parsed, we go to sleep.
163 // However, we have to be careful and use a 'while' loop here
164 // as it can happen that an item is added to the queue but this
165 // thread is woken up only after it has been removed again.
166 while(m_parserQueue.size() == 0 && m_keepParserThreadAlive) {
167 qCDebug(LOG_KILE_PARSER) << "going to sleep...";
168 emit(parsingQueueEmpty());
169 m_queueEmptyWaitCondition.wait(&m_parserMutex);
170 qCDebug(LOG_KILE_PARSER) << "woken up...";
171 }
172 // threads are woken up when an object of this class is destroyed; in
173 // that case the queue might still be empty
174 if(!m_keepParserThreadAlive) {
175 m_parserMutex.unlock();
176 // remaining queue elements are deleted in the destructor
177 return;
178 }
179 Q_ASSERT(m_parserQueue.size() > 0);
180 qCDebug(LOG_KILE_PARSER) << "queue length" << m_parserQueue.length();
181 // now, extract the head
182 currentParsedItem = m_parserQueue.dequeue();
183
184 m_keepParsingDocument = true;
185 m_currentlyParsedUrl = currentParsedItem->url;
186 emit(parsingStarted());
187 m_parserMutex.unlock();
188
189 Parser *parser = createParser(currentParsedItem);
190
191 ParserOutput *parserOutput = Q_NULLPTR;
192 if(parser) {
193 parserOutput = parser->parse();
194 }
195
196 delete currentParsedItem;
197 delete parser;
198
199 // we also emit when 'parserOutput == Q_NULLPTR' as this will be used to indicate
200 // that some error has occurred;
201 // as this call will be blocking, one has to make sure that no mutex is held
202 emit(parsingComplete(m_currentlyParsedUrl, parserOutput));
203 }
204 qCDebug(LOG_KILE_PARSER) << "leaving...";
205 // remaining queue elements are deleted in the destructor
206 }
207
DocumentParserThread(KileInfo * info,QObject * parent)208 DocumentParserThread::DocumentParserThread(KileInfo *info, QObject *parent)
209 : ParserThread(info, parent)
210 {
211 }
212
~DocumentParserThread()213 DocumentParserThread::~DocumentParserThread()
214 {
215 }
216
createParser(ParserInput * input)217 Parser* DocumentParserThread::createParser(ParserInput *input)
218 {
219 if(dynamic_cast<LaTeXParserInput*>(input)) {
220 return new LaTeXParser(this, dynamic_cast<LaTeXParserInput*>(input));
221 }
222 else if(dynamic_cast<BibTeXParserInput*>(input)) {
223 return new BibTeXParser(this, dynamic_cast<BibTeXParserInput*>(input));
224 }
225
226 return Q_NULLPTR;
227 }
228
addDocument(KileDocument::TextInfo * textInfo)229 void DocumentParserThread::addDocument(KileDocument::TextInfo *textInfo)
230 {
231 qCDebug(LOG_KILE_PARSER) << textInfo;
232 const QUrl url = m_ki->docManager()->urlFor(textInfo);
233 if(url.isEmpty()) { // if the url is empty (which can happen with new documents),
234 return; // we can't do anything as not even the results of the parsing can be displayed
235 }
236
237 ParserInput* newItem = Q_NULLPTR;
238 if(dynamic_cast<KileDocument::BibInfo*>(textInfo)) {
239 newItem = new BibTeXParserInput(url, textInfo->documentContents());
240 }
241 else {
242 newItem = new LaTeXParserInput(url, textInfo->documentContents(),
243 m_ki->extensions(),
244 textInfo->dictStructLevel(),
245 KileConfig::svShowSectioningLabels(),
246 KileConfig::svShowTodo());
247 }
248 addParserInput(newItem);
249
250 // It is not very useful to watch for the destruction of 'textInfo' here and stop the parsing
251 // for 'textInfo' whenever that happens as at that moment it probably won't have a document
252 // anymore nor would it still be associated with a project item.
253 // It is better to call 'removeDocument' from the point when 'textInfo' is going to be deleted!
254 }
255
removeDocument(KileDocument::TextInfo * textInfo)256 void DocumentParserThread::removeDocument(KileDocument::TextInfo *textInfo)
257 {
258 qCDebug(LOG_KILE_PARSER);
259 KTextEditor::Document *document = textInfo->getDoc();
260 if(!document) {
261 return;
262 }
263 removeParserInput(document->url());
264 }
265
removeDocument(const QUrl & url)266 void DocumentParserThread::removeDocument(const QUrl &url)
267 {
268 removeParserInput(url);
269 }
270
OutputParserThread(KileInfo * info,QObject * parent)271 OutputParserThread::OutputParserThread(KileInfo *info, QObject *parent)
272 : ParserThread(info, parent)
273 {
274 }
275
~OutputParserThread()276 OutputParserThread::~OutputParserThread()
277 {
278 }
279
createParser(ParserInput * input)280 Parser* OutputParserThread::createParser(ParserInput *input)
281 {
282 if(dynamic_cast<LaTeXOutputParserInput*>(input)) {
283 return new LaTeXOutputParser(this, dynamic_cast<LaTeXOutputParserInput*>(input));
284 }
285 return Q_NULLPTR;
286 }
287
addLaTeXLogFile(const QString & logFile,const QString & sourceFile,const QString & texFileName,int selrow,int docrow)288 void OutputParserThread::addLaTeXLogFile(const QString& logFile, const QString& sourceFile,
289 const QString& texFileName, int selrow, int docrow)
290 {
291 qCDebug(LOG_KILE_PARSER) << logFile << sourceFile;
292
293 ParserInput* newItem = new LaTeXOutputParserInput(QUrl::fromLocalFile(logFile), m_ki->extensions(),
294 sourceFile,
295 texFileName, selrow, docrow);
296 addParserInput(newItem);
297 }
298
removeFile(const QString & fileName)299 void OutputParserThread::removeFile(const QString& fileName)
300 {
301 removeParserInput(QUrl::fromLocalFile(fileName));
302 }
303
304 }
305
306