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