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 ®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 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