1 /*
2 * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors
3 *
4 * This file is part of glogg.
5 *
6 * glogg is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * glogg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with glogg. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <QFile>
21
22 #include "log.h"
23
24 #include "logfiltereddataworkerthread.h"
25 #include "logdata.h"
26
27 // Number of lines in each chunk to read
28 const int SearchOperation::nbLinesInChunk = 5000;
29
getAll(int * length,SearchResultArray * matches,qint64 * lines) const30 void SearchData::getAll( int* length, SearchResultArray* matches,
31 qint64* lines) const
32 {
33 QMutexLocker locker( &dataMutex_ );
34
35 *length = maxLength_;
36 *lines = nbLinesProcessed_;
37
38 // This is a copy (potentially slow)
39 *matches = matches_;
40 }
41
setAll(int length,SearchResultArray && matches)42 void SearchData::setAll( int length,
43 SearchResultArray&& matches )
44 {
45 QMutexLocker locker( &dataMutex_ );
46
47 maxLength_ = length;
48 matches_ = matches;
49 }
50
addAll(int length,const SearchResultArray & matches,LineNumber lines)51 void SearchData::addAll( int length,
52 const SearchResultArray& matches, LineNumber lines )
53 {
54 QMutexLocker locker( &dataMutex_ );
55
56 maxLength_ = qMax( maxLength_, length );
57 nbLinesProcessed_ = lines;
58
59 // This does a copy as we want the final array to be
60 // linear.
61 matches_.insert( std::end( matches_ ),
62 std::begin( matches ), std::end( matches ) );
63 }
64
getNbMatches() const65 LineNumber SearchData::getNbMatches() const
66 {
67 QMutexLocker locker( &dataMutex_ );
68
69 return matches_.size();
70 }
71
72 // This function starts searching from the end since we use it
73 // to remove the final match.
deleteMatch(LineNumber line)74 void SearchData::deleteMatch( LineNumber line )
75 {
76 QMutexLocker locker( &dataMutex_ );
77
78 SearchResultArray::iterator i = matches_.end();
79 while ( i != matches_.begin() ) {
80 i--;
81 const LineNumber this_line = i->lineNumber();
82 if ( this_line == line ) {
83 matches_.erase(i);
84 break;
85 }
86 // Exit if we have passed the line number to look for.
87 if ( this_line < line )
88 break;
89 }
90 }
91
clear()92 void SearchData::clear()
93 {
94 QMutexLocker locker( &dataMutex_ );
95
96 maxLength_ = 0;
97 nbLinesProcessed_ = 0;
98 matches_.clear();
99 }
100
LogFilteredDataWorkerThread(const LogData * sourceLogData)101 LogFilteredDataWorkerThread::LogFilteredDataWorkerThread(
102 const LogData* sourceLogData )
103 : QThread(), mutex_(), operationRequestedCond_(), nothingToDoCond_(), searchData_()
104 {
105 terminate_ = false;
106 interruptRequested_ = false;
107 operationRequested_ = NULL;
108
109 sourceLogData_ = sourceLogData;
110 }
111
~LogFilteredDataWorkerThread()112 LogFilteredDataWorkerThread::~LogFilteredDataWorkerThread()
113 {
114 {
115 QMutexLocker locker( &mutex_ );
116 terminate_ = true;
117 operationRequestedCond_.wakeAll();
118 }
119 wait();
120 }
121
search(const QRegularExpression & regExp)122 void LogFilteredDataWorkerThread::search( const QRegularExpression& regExp )
123 {
124 QMutexLocker locker( &mutex_ ); // to protect operationRequested_
125
126 LOG(logDEBUG) << "Search requested";
127
128 // If an operation is ongoing, we will block
129 while ( (operationRequested_ != NULL) )
130 nothingToDoCond_.wait( &mutex_ );
131
132 interruptRequested_ = false;
133 operationRequested_ = new FullSearchOperation( sourceLogData_,
134 regExp, &interruptRequested_ );
135 operationRequestedCond_.wakeAll();
136 }
137
updateSearch(const QRegularExpression & regExp,qint64 position)138 void LogFilteredDataWorkerThread::updateSearch(const QRegularExpression ®Exp, qint64 position )
139 {
140 QMutexLocker locker( &mutex_ ); // to protect operationRequested_
141
142 LOG(logDEBUG) << "Search requested";
143
144 // If an operation is ongoing, we will block
145 while ( (operationRequested_ != NULL) )
146 nothingToDoCond_.wait( &mutex_ );
147
148 interruptRequested_ = false;
149 operationRequested_ = new UpdateSearchOperation( sourceLogData_,
150 regExp, &interruptRequested_, position );
151 operationRequestedCond_.wakeAll();
152 }
153
interrupt()154 void LogFilteredDataWorkerThread::interrupt()
155 {
156 LOG(logDEBUG) << "Search interruption requested";
157
158 // No mutex here, setting a bool is probably atomic!
159 interruptRequested_ = true;
160
161 // We wait for the interruption to be done
162 {
163 QMutexLocker locker( &mutex_ );
164 while ( (operationRequested_ != NULL) )
165 nothingToDoCond_.wait( &mutex_ );
166 }
167 }
168
169 // This will do an atomic copy of the object
getSearchResult(int * maxLength,SearchResultArray * searchMatches,qint64 * nbLinesProcessed)170 void LogFilteredDataWorkerThread::getSearchResult(
171 int* maxLength, SearchResultArray* searchMatches, qint64* nbLinesProcessed )
172 {
173 searchData_.getAll( maxLength, searchMatches, nbLinesProcessed );
174 }
175
176 // This is the thread's main loop
run()177 void LogFilteredDataWorkerThread::run()
178 {
179 QMutexLocker locker( &mutex_ );
180
181 forever {
182 while ( (terminate_ == false) && (operationRequested_ == NULL) )
183 operationRequestedCond_.wait( &mutex_ );
184 LOG(logDEBUG) << "Worker thread signaled";
185
186 // Look at what needs to be done
187 if ( terminate_ )
188 return; // We must die
189
190 if ( operationRequested_ ) {
191 connect( operationRequested_, SIGNAL( searchProgressed( int, int, qint64 ) ),
192 this, SIGNAL( searchProgressed( int, int, qint64 ) ) );
193
194 // Run the search operation
195 operationRequested_->start( searchData_ );
196
197 LOG(logDEBUG) << "... finished copy in workerThread.";
198
199 emit searchFinished();
200 delete operationRequested_;
201 operationRequested_ = NULL;
202 nothingToDoCond_.wakeAll();
203 }
204 }
205 }
206
207 //
208 // Operations implementation
209 //
210
SearchOperation(const LogData * sourceLogData,const QRegularExpression & regExp,bool * interruptRequest)211 SearchOperation::SearchOperation( const LogData* sourceLogData,
212 const QRegularExpression& regExp, bool* interruptRequest )
213 : regexp_( regExp ), sourceLogData_( sourceLogData )
214 {
215 interruptRequested_ = interruptRequest;
216 }
217
doSearch(SearchData & searchData,qint64 initialLine)218 void SearchOperation::doSearch( SearchData& searchData, qint64 initialLine )
219 {
220 const qint64 nbSourceLines = sourceLogData_->getNbLine();
221 int maxLength = 0;
222 int nbMatches = searchData.getNbMatches();
223 SearchResultArray currentList = SearchResultArray();
224
225 // Ensure no re-alloc will be done
226 currentList.reserve( nbLinesInChunk );
227
228 LOG(logDEBUG) << "Searching from line " << initialLine << " to " << nbSourceLines;
229
230 for ( qint64 i = initialLine; i < nbSourceLines; i += nbLinesInChunk ) {
231 if ( *interruptRequested_ )
232 break;
233
234 const int percentage = ( i - initialLine ) * 100 / ( nbSourceLines - initialLine );
235 emit searchProgressed( nbMatches, percentage, initialLine );
236
237 const QStringList lines = sourceLogData_->getLines( i,
238 qMin( nbLinesInChunk, (int) ( nbSourceLines - i ) ) );
239 LOG(logDEBUG) << "Chunk starting at " << i <<
240 ", " << lines.size() << " lines read.";
241
242 int j = 0;
243 for ( ; j < lines.size(); j++ ) {
244 if ( regexp_.match( lines[j] ).hasMatch() ) {
245 // FIXME: increase perf by removing temporary
246 const int length = sourceLogData_->getExpandedLineString(i+j).length();
247 if ( length > maxLength )
248 maxLength = length;
249 currentList.push_back( MatchingLine( i+j ) );
250 nbMatches++;
251 }
252 }
253
254 // After each block, copy the data to shared data
255 // and update the client
256 searchData.addAll( maxLength, currentList, i+j );
257 currentList.clear();
258 }
259
260 emit searchProgressed( nbMatches, 100, initialLine );
261 }
262
263 // Called in the worker thread's context
start(SearchData & searchData)264 void FullSearchOperation::start( SearchData& searchData )
265 {
266 // Clear the shared data
267 searchData.clear();
268
269 doSearch( searchData, 0 );
270 }
271
272 // Called in the worker thread's context
start(SearchData & searchData)273 void UpdateSearchOperation::start( SearchData& searchData )
274 {
275 qint64 initial_line = initialPosition_;
276
277 if ( initial_line >= 1 ) {
278 // We need to re-search the last line because it might have
279 // been updated (if it was not LF-terminated)
280 --initial_line;
281 // In case the last line matched, we don't want it to match twice.
282 searchData.deleteMatch( initial_line );
283 }
284
285 doSearch( searchData, initial_line );
286 }
287