xref: /glogg/src/data/logfiltereddataworkerthread.cpp (revision f869e41d2c129cd0f2f3eccb5e9d0d80a5998201)
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 
30 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 
42 void SearchData::setAll( int length,
43         SearchResultArray&& matches )
44 {
45     QMutexLocker locker( &dataMutex_ );
46 
47     maxLength_  = length;
48     matches_    = matches;
49 }
50 
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 
65 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.
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 
92 void SearchData::clear()
93 {
94     QMutexLocker locker( &dataMutex_ );
95 
96     maxLength_        = 0;
97     nbLinesProcessed_ = 0;
98     matches_.clear();
99 }
100 
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 
112 LogFilteredDataWorkerThread::~LogFilteredDataWorkerThread()
113 {
114     {
115         QMutexLocker locker( &mutex_ );
116         terminate_ = true;
117         operationRequestedCond_.wakeAll();
118     }
119     wait();
120 }
121 
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 
138 void LogFilteredDataWorkerThread::updateSearch(const QRegularExpression &regExp, 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 
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
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
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 
211 SearchOperation::SearchOperation( const LogData* sourceLogData,
212         const QRegularExpression& regExp, bool* interruptRequest )
213     : regexp_( regExp ), sourceLogData_( sourceLogData )
214 {
215     interruptRequested_ = interruptRequest;
216 }
217 
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
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
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