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