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 *matches = matches_; 37 *lines = nbLinesProcessed_; 38 } 39 40 void SearchData::setAll( int length, 41 const SearchResultArray& matches ) 42 { 43 QMutexLocker locker( &dataMutex_ ); 44 45 maxLength_ = length; 46 matches_ = matches; 47 } 48 49 void SearchData::addAll( int length, 50 const SearchResultArray& matches, qint64 lines ) 51 { 52 QMutexLocker locker( &dataMutex_ ); 53 54 maxLength_ = qMax( maxLength_, length ); 55 matches_ += matches; 56 nbLinesProcessed_ = lines; 57 } 58 59 int SearchData::getNbMatches() const 60 { 61 QMutexLocker locker( &dataMutex_ ); 62 63 return matches_.count(); 64 } 65 66 // This function starts searching from the end since we use it 67 // to remove the final match. 68 void SearchData::deleteMatch( qint64 line ) 69 { 70 QMutexLocker locker( &dataMutex_ ); 71 72 SearchResultArray::iterator i = matches_.end(); 73 while ( i != matches_.begin() ) { 74 i--; 75 const int this_line = i->lineNumber(); 76 if ( this_line == line ) { 77 matches_.erase(i); 78 break; 79 } 80 // Exit if we have passed the line number to look for. 81 if ( this_line < line ) 82 break; 83 } 84 } 85 86 void SearchData::clear() 87 { 88 QMutexLocker locker( &dataMutex_ ); 89 90 maxLength_ = 0; 91 nbLinesProcessed_ = 0; 92 matches_.clear(); 93 } 94 95 96 97 LogFilteredDataWorkerThread::LogFilteredDataWorkerThread( 98 const LogData* sourceLogData ) 99 : QThread(), mutex_(), operationRequestedCond_(), nothingToDoCond_(), searchData_() 100 { 101 terminate_ = false; 102 interruptRequested_ = false; 103 operationRequested_ = NULL; 104 105 sourceLogData_ = sourceLogData; 106 } 107 108 LogFilteredDataWorkerThread::~LogFilteredDataWorkerThread() 109 { 110 { 111 QMutexLocker locker( &mutex_ ); 112 terminate_ = true; 113 operationRequestedCond_.wakeAll(); 114 } 115 wait(); 116 } 117 118 void LogFilteredDataWorkerThread::search( const QRegExp& regExp ) 119 { 120 QMutexLocker locker( &mutex_ ); // to protect operationRequested_ 121 122 LOG(logDEBUG) << "Search requested"; 123 124 // If an operation is ongoing, we will block 125 while ( (operationRequested_ != NULL) ) 126 nothingToDoCond_.wait( &mutex_ ); 127 128 interruptRequested_ = false; 129 operationRequested_ = new FullSearchOperation( sourceLogData_, 130 regExp, &interruptRequested_ ); 131 operationRequestedCond_.wakeAll(); 132 } 133 134 void LogFilteredDataWorkerThread::updateSearch( const QRegExp& regExp, qint64 position ) 135 { 136 QMutexLocker locker( &mutex_ ); // to protect operationRequested_ 137 138 LOG(logDEBUG) << "Search requested"; 139 140 // If an operation is ongoing, we will block 141 while ( (operationRequested_ != NULL) ) 142 nothingToDoCond_.wait( &mutex_ ); 143 144 interruptRequested_ = false; 145 operationRequested_ = new UpdateSearchOperation( sourceLogData_, 146 regExp, &interruptRequested_, position ); 147 operationRequestedCond_.wakeAll(); 148 } 149 150 void LogFilteredDataWorkerThread::interrupt() 151 { 152 LOG(logDEBUG) << "Search interruption requested"; 153 154 // No mutex here, setting a bool is probably atomic! 155 interruptRequested_ = true; 156 157 // We wait for the interruption to be done 158 { 159 QMutexLocker locker( &mutex_ ); 160 while ( (operationRequested_ != NULL) ) 161 nothingToDoCond_.wait( &mutex_ ); 162 } 163 } 164 165 // This will do an atomic copy of the object 166 // (hopefully fast as we use Qt containers) 167 void LogFilteredDataWorkerThread::getSearchResult( 168 int* maxLength, SearchResultArray* searchMatches, qint64* nbLinesProcessed ) 169 { 170 searchData_.getAll( maxLength, searchMatches, nbLinesProcessed ); 171 } 172 173 // This is the thread's main loop 174 void LogFilteredDataWorkerThread::run() 175 { 176 QMutexLocker locker( &mutex_ ); 177 178 forever { 179 while ( (terminate_ == false) && (operationRequested_ == NULL) ) 180 operationRequestedCond_.wait( &mutex_ ); 181 LOG(logDEBUG) << "Worker thread signaled"; 182 183 // Look at what needs to be done 184 if ( terminate_ ) 185 return; // We must die 186 187 if ( operationRequested_ ) { 188 connect( operationRequested_, SIGNAL( searchProgressed( int, int ) ), 189 this, SIGNAL( searchProgressed( int, int ) ) ); 190 191 // Run the search operation 192 operationRequested_->start( searchData_ ); 193 194 LOG(logDEBUG) << "... finished copy in workerThread."; 195 196 emit searchFinished(); 197 delete operationRequested_; 198 operationRequested_ = NULL; 199 nothingToDoCond_.wakeAll(); 200 } 201 } 202 } 203 204 // 205 // Operations implementation 206 // 207 208 SearchOperation::SearchOperation( const LogData* sourceLogData, 209 const QRegExp& regExp, bool* interruptRequest ) 210 : regexp_( regExp ), sourceLogData_( sourceLogData ) 211 { 212 interruptRequested_ = interruptRequest; 213 } 214 215 void SearchOperation::doSearch( SearchData& searchData, qint64 initialLine ) 216 { 217 const qint64 nbSourceLines = sourceLogData_->getNbLine(); 218 int maxLength = 0; 219 int nbMatches = searchData.getNbMatches(); 220 SearchResultArray currentList = SearchResultArray(); 221 222 LOG(logDEBUG) << "Searching from line " << initialLine << " to " << nbSourceLines; 223 224 for ( qint64 i = initialLine; i < nbSourceLines; i += nbLinesInChunk ) { 225 if ( *interruptRequested_ ) 226 break; 227 228 const int percentage = ( i - initialLine ) * 100 / ( nbSourceLines - initialLine ); 229 emit searchProgressed( nbMatches, percentage ); 230 231 const QStringList lines = sourceLogData_->getLines( i, 232 qMin( nbLinesInChunk, (int) ( nbSourceLines - i ) ) ); 233 LOG(logDEBUG) << "Chunk starting at " << i << 234 ", " << lines.size() << " lines read."; 235 236 int j = 0; 237 for ( ; j < lines.size(); j++ ) { 238 if ( regexp_.indexIn( lines[j] ) != -1 ) { 239 const int length = sourceLogData_->getExpandedLineString(i+j).length(); 240 if ( length > maxLength ) 241 maxLength = length; 242 MatchingLine match( i+j ); 243 currentList.append( match ); 244 nbMatches++; 245 } 246 } 247 248 // After each block, copy the data to shared data 249 // and update the client 250 searchData.addAll( maxLength, currentList, i+j ); 251 currentList.clear(); 252 } 253 254 emit searchProgressed( nbMatches, 100 ); 255 } 256 257 // Called in the worker thread's context 258 void FullSearchOperation::start( SearchData& searchData ) 259 { 260 // Clear the shared data 261 searchData.clear(); 262 263 doSearch( searchData, 0 ); 264 } 265 266 // Called in the worker thread's context 267 void UpdateSearchOperation::start( SearchData& searchData ) 268 { 269 qint64 initial_line = initialPosition_; 270 271 if ( initial_line >= 1 ) { 272 // We need to re-search the last line because it might have 273 // been updated (if it was not LF-terminated) 274 --initial_line; 275 // In case the last line matched, we don't want it to match twice. 276 searchData.deleteMatch( initial_line ); 277 } 278 279 doSearch( searchData, initial_line ); 280 } 281