1 /* 2 * Copyright (C) 2009, 2010, 2011, 2012 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 // This file implements LogFilteredData 21 // It stores a pointer to the LogData that created it, 22 // so should always be destroyed before the LogData. 23 24 #include "log.h" 25 26 #include <QString> 27 #include <cassert> 28 #include <limits> 29 30 #include "utils.h" 31 #include "logdata.h" 32 #include "marks.h" 33 #include "logfiltereddata.h" 34 35 // Creates an empty set. It must be possible to display it without error. 36 // FIXME 37 LogFilteredData::LogFilteredData() : AbstractLogData(), 38 matchingLineList( QList<MatchingLine>() ), 39 currentRegExp_(), 40 visibility_(), 41 filteredItemsCache_() 42 { 43 matchingLineList = QList<MatchingLine>(); 44 /* Prevent any more searching */ 45 maxLength_ = 0; 46 maxLengthMarks_ = 0; 47 searchDone_ = true; 48 visibility_ = MarksAndMatches; 49 50 workerThread_ = new LogFilteredDataWorkerThread( NULL ); 51 marks_ = new Marks(); 52 53 filteredItemsCacheDirty_ = true; 54 } 55 56 // Usual constructor: just copy the data, the search is started by runSearch() 57 LogFilteredData::LogFilteredData( const LogData* logData ) 58 : AbstractLogData(), 59 matchingLineList( SearchResultArray() ), 60 currentRegExp_(), 61 visibility_(), 62 filteredItemsCache_() 63 { 64 // Starts with an empty result list 65 maxLength_ = 0; 66 maxLengthMarks_ = 0; 67 nbLinesProcessed_ = 0; 68 69 sourceLogData_ = logData; 70 71 searchDone_ = false; 72 73 visibility_ = MarksAndMatches; 74 75 workerThread_ = new LogFilteredDataWorkerThread( logData ); 76 marks_ = new Marks(); 77 78 filteredItemsCacheDirty_ = true; 79 80 // Forward the update signal 81 connect( workerThread_, SIGNAL( searchProgressed( int, int ) ), 82 this, SLOT( handleSearchProgressed( int, int ) ) ); 83 84 // Starts the worker thread 85 workerThread_->start(); 86 } 87 88 // 89 // Public functions 90 // 91 92 // Run the search and send newDataAvailable() signals. 93 void LogFilteredData::runSearch( const QRegExp& regExp ) 94 { 95 LOG(logDEBUG) << "Entering runSearch"; 96 97 // Reset the search 98 currentRegExp_ = regExp; 99 matchingLineList.clear(); 100 maxLength_ = 0; 101 maxLengthMarks_ = 0; 102 103 workerThread_->search( currentRegExp_ ); 104 } 105 106 void LogFilteredData::updateSearch() 107 { 108 LOG(logDEBUG) << "Entering updateSearch"; 109 110 workerThread_->updateSearch( currentRegExp_, nbLinesProcessed_ ); 111 } 112 113 void LogFilteredData::interruptSearch() 114 { 115 LOG(logDEBUG) << "Entering interruptSearch"; 116 117 workerThread_->interrupt(); 118 } 119 120 void LogFilteredData::clearSearch() 121 { 122 currentRegExp_ = QRegExp(); 123 matchingLineList.clear(); 124 maxLength_ = 0; 125 filteredItemsCacheDirty_ = true; 126 } 127 128 qint64 LogFilteredData::getMatchingLineNumber( int matchNum ) const 129 { 130 qint64 matchingLine = findLogDataLine( matchNum ); 131 132 return matchingLine; 133 } 134 135 // Scan the list for the 'lineNumber' passed 136 bool LogFilteredData::isLineInMatchingList( qint64 lineNumber ) 137 { 138 int index; // Not used 139 return lookupLineNumber<SearchResultArray>( 140 matchingLineList, lineNumber, &index); 141 } 142 143 144 qint64 LogFilteredData::getNbTotalLines() const 145 { 146 return sourceLogData_->getNbLine(); 147 } 148 149 int LogFilteredData::getNbMatches() const 150 { 151 return matchingLineList.size(); 152 } 153 154 int LogFilteredData::getNbMarks() const 155 { 156 return marks_->size(); 157 } 158 159 LogFilteredData::FilteredLineType 160 LogFilteredData::filteredLineTypeByIndex( int index ) const 161 { 162 // If we are only showing one type, the line is there because 163 // it is of this type. 164 if ( visibility_ == MatchesOnly ) 165 return Match; 166 else if ( visibility_ == MarksOnly ) 167 return Mark; 168 else { 169 // If it is MarksAndMatches, we have to look. 170 // Regenerate the cache if needed 171 if ( filteredItemsCacheDirty_ ) 172 regenerateFilteredItemsCache(); 173 174 return filteredItemsCache_[ index ].type(); 175 } 176 } 177 178 // Delegation to our Marks object 179 180 void LogFilteredData::addMark( qint64 line, QChar mark ) 181 { 182 assert( marks_ ); 183 184 if ( ( line >= 0 ) && ( line < sourceLogData_->getNbLine() ) ) { 185 marks_->addMark( line, mark ); 186 maxLengthMarks_ = qMax( maxLengthMarks_, 187 sourceLogData_->getLineLength( line ) ); 188 filteredItemsCacheDirty_ = true; 189 } 190 else 191 LOG(logERROR) << "LogFilteredData::addMark\ 192 trying to create a mark outside of the file."; 193 } 194 195 qint64 LogFilteredData::getMark( QChar mark ) const 196 { 197 assert( marks_ ); 198 199 return marks_->getMark( mark ); 200 } 201 202 bool LogFilteredData::isLineMarked( qint64 line ) const 203 { 204 assert( marks_ ); 205 206 return marks_->isLineMarked( line ); 207 } 208 209 void LogFilteredData::deleteMark( QChar mark ) 210 { 211 assert( marks_ ); 212 213 marks_->deleteMark( mark ); 214 filteredItemsCacheDirty_ = true; 215 216 // FIXME: maxLengthMarks_ 217 } 218 219 void LogFilteredData::deleteMark( qint64 line ) 220 { 221 assert( marks_ ); 222 223 marks_->deleteMark( line ); 224 filteredItemsCacheDirty_ = true; 225 226 // Now update the max length if needed 227 if ( sourceLogData_->getLineLength( line ) >= maxLengthMarks_ ) { 228 LOG(logDEBUG) << "deleteMark recalculating longest mark"; 229 maxLengthMarks_ = 0; 230 for ( Marks::const_iterator i = marks_->begin(); 231 i != marks_->end(); ++i ) { 232 LOG(logDEBUG) << "line " << i->lineNumber(); 233 maxLengthMarks_ = qMax( maxLengthMarks_, 234 sourceLogData_->getLineLength( i->lineNumber() ) ); 235 } 236 } 237 } 238 239 void LogFilteredData::clearMarks() 240 { 241 assert( marks_ ); 242 243 marks_->clear(); 244 filteredItemsCacheDirty_ = true; 245 maxLengthMarks_ = 0; 246 } 247 248 void LogFilteredData::setVisibility( Visibility visi ) 249 { 250 visibility_ = visi; 251 } 252 253 // 254 // Slots 255 // 256 void LogFilteredData::handleSearchProgressed( int nbMatches, int progress ) 257 { 258 LOG(logDEBUG) << "LogFilteredData::handleSearchProgressed matches=" 259 << nbMatches << " progress=" << progress; 260 261 // searchDone_ = true; 262 workerThread_->getSearchResult( &maxLength_, &matchingLineList, &nbLinesProcessed_ ); 263 filteredItemsCacheDirty_ = true; 264 265 emit searchProgressed( nbMatches, progress ); 266 } 267 268 qint64 LogFilteredData::findLogDataLine( qint64 lineNum ) const 269 { 270 qint64 line = 0; 271 if ( visibility_ == MatchesOnly ) { 272 if ( lineNum < matchingLineList.size() ) { 273 line = matchingLineList[lineNum].lineNumber(); 274 } 275 else { 276 LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; 277 } 278 } 279 else if ( visibility_ == MarksOnly ) { 280 if ( lineNum < marks_->size() ) 281 line = marks_->getLineMarkedByIndex( lineNum ); 282 else 283 LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; 284 } 285 else { 286 // Regenerate the cache if needed 287 if ( filteredItemsCacheDirty_ ) 288 regenerateFilteredItemsCache(); 289 290 if ( lineNum < filteredItemsCache_.size() ) 291 line = filteredItemsCache_[ lineNum ].lineNumber(); 292 else 293 LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; 294 } 295 296 return line; 297 } 298 299 // Implementation of the virtual function. 300 QString LogFilteredData::doGetLineString( qint64 lineNum ) const 301 { 302 qint64 line = findLogDataLine( lineNum ); 303 304 QString string = sourceLogData_->getLineString( line ); 305 return string; 306 } 307 308 // Implementation of the virtual function. 309 QString LogFilteredData::doGetExpandedLineString( qint64 lineNum ) const 310 { 311 qint64 line = findLogDataLine( lineNum ); 312 313 QString string = sourceLogData_->getExpandedLineString( line ); 314 return string; 315 } 316 317 // Implementation of the virtual function. 318 QStringList LogFilteredData::doGetLines( qint64 first_line, int number ) const 319 { 320 QStringList list; 321 322 for ( int i = first_line; i < first_line + number; i++ ) { 323 list.append( doGetLineString( i ) ); 324 } 325 326 return list; 327 } 328 329 // Implementation of the virtual function. 330 QStringList LogFilteredData::doGetExpandedLines( qint64 first_line, int number ) const 331 { 332 QStringList list; 333 334 for ( int i = first_line; i < first_line + number; i++ ) { 335 list.append( doGetExpandedLineString( i ) ); 336 } 337 338 return list; 339 } 340 341 // Implementation of the virtual function. 342 qint64 LogFilteredData::doGetNbLine() const 343 { 344 qint64 nbLines; 345 346 if ( visibility_ == MatchesOnly ) 347 nbLines = matchingLineList.size(); 348 else if ( visibility_ == MarksOnly ) 349 nbLines = marks_->size(); 350 else { 351 // Regenerate the cache if needed (hopefully most of the time 352 // it won't be necessarily) 353 if ( filteredItemsCacheDirty_ ) 354 regenerateFilteredItemsCache(); 355 nbLines = filteredItemsCache_.size(); 356 } 357 358 return nbLines; 359 } 360 361 // Implementation of the virtual function. 362 int LogFilteredData::doGetMaxLength() const 363 { 364 int max_length; 365 366 if ( visibility_ == MatchesOnly ) 367 max_length = maxLength_; 368 else if ( visibility_ == MarksOnly ) 369 max_length = maxLengthMarks_; 370 else 371 max_length = qMax( maxLength_, maxLengthMarks_ ); 372 373 return max_length; 374 } 375 376 // Implementation of the virtual function. 377 int LogFilteredData::doGetLineLength( qint64 lineNum ) const 378 { 379 qint64 line = findLogDataLine( lineNum ); 380 return sourceLogData_->getExpandedLineString( line ).length(); 381 } 382 383 // TODO: We might be a bit smarter and not regenerate the whole thing when 384 // e.g. stuff is added at the end of the search. 385 void LogFilteredData::regenerateFilteredItemsCache() const 386 { 387 LOG(logDEBUG) << "regenerateFilteredItemsCache"; 388 389 filteredItemsCache_.clear(); 390 filteredItemsCache_.reserve( matchingLineList.size() + marks_->size() ); 391 // (it's an overestimate but probably not by much so it's fine) 392 393 QList<MatchingLine>::const_iterator i = matchingLineList.begin(); 394 Marks::const_iterator j = marks_->begin(); 395 396 while ( ( i != matchingLineList.end() ) || ( j != marks_->end() ) ) { 397 qint64 next_mark = 398 ( j != marks_->end() ) ? j->lineNumber() : std::numeric_limits<qint64>::max(); 399 qint64 next_match = 400 ( i != matchingLineList.end() ) ? i->lineNumber() : std::numeric_limits<qint64>::max(); 401 // We choose a Mark over a Match if a line is both, just an arbitrary choice really. 402 if ( next_mark <= next_match ) { 403 // LOG(logDEBUG) << "Add mark at " << next_mark; 404 filteredItemsCache_.append( FilteredItem( next_mark, Mark ) ); 405 if ( j != marks_->end() ) 406 ++j; 407 if ( ( next_mark == next_match ) && ( i != matchingLineList.end() ) ) 408 ++i; // Case when it's both match and mark. 409 } 410 else { 411 // LOG(logDEBUG) << "Add match at " << next_match; 412 filteredItemsCache_.append( FilteredItem( next_match, Match ) ); 413 if ( i != matchingLineList.end() ) 414 ++i; 415 } 416 } 417 418 filteredItemsCacheDirty_ = false; 419 420 LOG(logDEBUG) << "finished regenerateFilteredItemsCache"; 421 } 422