1 /* 2 * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2017 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 matching_lines_(), 39 currentRegExp_(), 40 visibility_(), 41 filteredItemsCache_(), 42 workerThread_( nullptr ), 43 marks_() 44 { 45 /* Prevent any more searching */ 46 maxLength_ = 0; 47 maxLengthMarks_ = 0; 48 searchDone_ = true; 49 visibility_ = MarksAndMatches; 50 51 filteredItemsCacheDirty_ = true; 52 } 53 54 // Usual constructor: just copy the data, the search is started by runSearch() 55 LogFilteredData::LogFilteredData( const LogData* logData ) 56 : AbstractLogData(), 57 matching_lines_( SearchResultArray() ), 58 currentRegExp_(), 59 visibility_(), 60 filteredItemsCache_(), 61 workerThread_( logData ), 62 marks_() 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 filteredItemsCacheDirty_ = true; 76 77 // Forward the update signal 78 connect( &workerThread_, SIGNAL( searchProgressed( int, int ) ), 79 this, SLOT( handleSearchProgressed( int, int ) ) ); 80 81 // Starts the worker thread 82 workerThread_.start(); 83 } 84 85 LogFilteredData::~LogFilteredData() 86 { 87 // FIXME 88 // workerThread_.stop(); 89 } 90 91 // 92 // Public functions 93 // 94 95 // Run the search and send newDataAvailable() signals. 96 void LogFilteredData::runSearch( const QRegularExpression& regExp ) 97 { 98 LOG(logDEBUG) << "Entering runSearch"; 99 100 clearSearch(); 101 currentRegExp_ = regExp; 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_ = QRegularExpression(); 123 matching_lines_.clear(); 124 maxLength_ = 0; 125 maxLengthMarks_ = 0; 126 nbLinesProcessed_ = 0; 127 filteredItemsCacheDirty_ = true; 128 } 129 130 qint64 LogFilteredData::getMatchingLineNumber( int matchNum ) const 131 { 132 qint64 matchingLine = findLogDataLine( matchNum ); 133 134 return matchingLine; 135 } 136 137 // Scan the list for the 'lineNumber' passed 138 bool LogFilteredData::isLineInMatchingList( qint64 lineNumber ) 139 { 140 int index; // Not used 141 return lookupLineNumber<SearchResultArray>( 142 matching_lines_, lineNumber, &index); 143 } 144 145 int LogFilteredData::getLineIndexNumber( quint64 lineNumber ) const 146 { 147 int lineIndex = findFilteredLine( lineNumber ); 148 return lineIndex; 149 } 150 151 LineNumber LogFilteredData::getNbTotalLines() const 152 { 153 return sourceLogData_->getNbLine(); 154 } 155 156 LineNumber LogFilteredData::getNbMatches() const 157 { 158 return matching_lines_.size(); 159 } 160 161 LineNumber LogFilteredData::getNbMarks() const 162 { 163 return marks_.size(); 164 } 165 166 LogFilteredData::FilteredLineType 167 LogFilteredData::filteredLineTypeByIndex( int index ) const 168 { 169 // If we are only showing one type, the line is there because 170 // it is of this type. 171 if ( visibility_ == MatchesOnly ) 172 return Match; 173 else if ( visibility_ == MarksOnly ) 174 return Mark; 175 else { 176 // If it is MarksAndMatches, we have to look. 177 // Regenerate the cache if needed 178 if ( filteredItemsCacheDirty_ ) 179 regenerateFilteredItemsCache(); 180 181 return filteredItemsCache_[ index ].type(); 182 } 183 } 184 185 // Delegation to our Marks object 186 187 void LogFilteredData::addMark( qint64 line, QChar mark ) 188 { 189 if ( ( line >= 0 ) && ( line < sourceLogData_->getNbLine() ) ) { 190 marks_.addMark( line, mark ); 191 maxLengthMarks_ = qMax( maxLengthMarks_, 192 sourceLogData_->getLineLength( line ) ); 193 filteredItemsCacheDirty_ = true; 194 } 195 else 196 LOG(logERROR) << "LogFilteredData::addMark\ 197 trying to create a mark outside of the file."; 198 } 199 200 qint64 LogFilteredData::getMark( QChar mark ) const 201 { 202 return marks_.getMark( mark ); 203 } 204 205 bool LogFilteredData::isLineMarked( qint64 line ) const 206 { 207 return marks_.isLineMarked( line ); 208 } 209 210 qint64 LogFilteredData::getMarkAfter( qint64 line ) const 211 { 212 qint64 marked_line = -1; 213 214 for ( auto i = marks_.begin(); i != marks_.end(); ++i ) { 215 if ( i->lineNumber() > line ) { 216 marked_line = i->lineNumber(); 217 break; 218 } 219 } 220 221 return marked_line; 222 } 223 224 qint64 LogFilteredData::getMarkBefore( qint64 line ) const 225 { 226 qint64 marked_line = -1; 227 228 for ( auto i = marks_.begin(); i != marks_.end(); ++i ) { 229 if ( i->lineNumber() >= line ) { 230 break; 231 } 232 marked_line = i->lineNumber(); 233 } 234 235 return marked_line; 236 } 237 238 void LogFilteredData::deleteMark( QChar mark ) 239 { 240 marks_.deleteMark( mark ); 241 filteredItemsCacheDirty_ = true; 242 243 // FIXME: maxLengthMarks_ 244 } 245 246 void LogFilteredData::deleteMark( qint64 line ) 247 { 248 marks_.deleteMark( line ); 249 filteredItemsCacheDirty_ = true; 250 251 // Now update the max length if needed 252 if ( sourceLogData_->getLineLength( line ) >= maxLengthMarks_ ) { 253 LOG(logDEBUG) << "deleteMark recalculating longest mark"; 254 maxLengthMarks_ = 0; 255 for ( Marks::const_iterator i = marks_.begin(); 256 i != marks_.end(); ++i ) { 257 LOG(logDEBUG) << "line " << i->lineNumber(); 258 maxLengthMarks_ = qMax( maxLengthMarks_, 259 sourceLogData_->getLineLength( i->lineNumber() ) ); 260 } 261 } 262 } 263 264 void LogFilteredData::clearMarks() 265 { 266 marks_.clear(); 267 filteredItemsCacheDirty_ = true; 268 maxLengthMarks_ = 0; 269 } 270 271 void LogFilteredData::setVisibility( Visibility visi ) 272 { 273 visibility_ = visi; 274 } 275 276 // 277 // Slots 278 // 279 void LogFilteredData::handleSearchProgressed( int nbMatches, int progress ) 280 { 281 LOG(logDEBUG) << "LogFilteredData::handleSearchProgressed matches=" 282 << nbMatches << " progress=" << progress; 283 284 // searchDone_ = true; 285 workerThread_.getSearchResult( &maxLength_, &matching_lines_, &nbLinesProcessed_ ); 286 filteredItemsCacheDirty_ = true; 287 288 emit searchProgressed( nbMatches, progress ); 289 } 290 291 LineNumber LogFilteredData::findLogDataLine( LineNumber lineNum ) const 292 { 293 LineNumber line = std::numeric_limits<LineNumber>::max(); 294 if ( visibility_ == MatchesOnly ) { 295 if ( lineNum < matching_lines_.size() ) { 296 line = matching_lines_[lineNum].lineNumber(); 297 } 298 else { 299 LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; 300 } 301 } 302 else if ( visibility_ == MarksOnly ) { 303 if ( lineNum < marks_.size() ) 304 line = marks_.getLineMarkedByIndex( lineNum ); 305 else 306 LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; 307 } 308 else { 309 // Regenerate the cache if needed 310 if ( filteredItemsCacheDirty_ ) 311 regenerateFilteredItemsCache(); 312 313 if ( lineNum < filteredItemsCache_.size() ) 314 line = filteredItemsCache_[ lineNum ].lineNumber(); 315 else 316 LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; 317 } 318 319 return line; 320 } 321 322 LineNumber LogFilteredData::findFilteredLine( LineNumber lineNum ) const 323 { 324 LineNumber lineIndex = std::numeric_limits<LineNumber>::max(); 325 326 if ( visibility_ == MatchesOnly ) { 327 lineIndex = lookupLineNumber( matching_lines_.begin(), 328 matching_lines_.end(), 329 lineNum ); 330 } 331 else if ( visibility_ == MarksOnly ) { 332 lineIndex = lookupLineNumber( marks_.begin(), 333 marks_.end(), 334 lineNum ); 335 } 336 else { 337 // Regenerate the cache if needed 338 if ( filteredItemsCacheDirty_ ) { 339 regenerateFilteredItemsCache(); 340 } 341 342 lineIndex = lookupLineNumber( filteredItemsCache_.begin(), 343 filteredItemsCache_.end(), 344 lineNum ); 345 } 346 347 return lineIndex; 348 } 349 350 // Implementation of the virtual function. 351 QString LogFilteredData::doGetLineString( qint64 lineNum ) const 352 { 353 qint64 line = findLogDataLine( lineNum ); 354 355 QString string = sourceLogData_->getLineString( line ); 356 return string; 357 } 358 359 // Implementation of the virtual function. 360 QString LogFilteredData::doGetExpandedLineString( qint64 lineNum ) const 361 { 362 qint64 line = findLogDataLine( lineNum ); 363 364 QString string = sourceLogData_->getExpandedLineString( line ); 365 return string; 366 } 367 368 // Implementation of the virtual function. 369 QStringList LogFilteredData::doGetLines( qint64 first_line, int number ) const 370 { 371 QStringList list; 372 373 for ( int i = first_line; i < first_line + number; i++ ) { 374 list.append( doGetLineString( i ) ); 375 } 376 377 return list; 378 } 379 380 // Implementation of the virtual function. 381 QStringList LogFilteredData::doGetExpandedLines( qint64 first_line, int number ) const 382 { 383 QStringList list; 384 385 for ( int i = first_line; i < first_line + number; i++ ) { 386 list.append( doGetExpandedLineString( i ) ); 387 } 388 389 return list; 390 } 391 392 // Implementation of the virtual function. 393 qint64 LogFilteredData::doGetNbLine() const 394 { 395 qint64 nbLines; 396 397 if ( visibility_ == MatchesOnly ) 398 nbLines = matching_lines_.size(); 399 else if ( visibility_ == MarksOnly ) 400 nbLines = marks_.size(); 401 else { 402 // Regenerate the cache if needed (hopefully most of the time 403 // it won't be necessarily) 404 if ( filteredItemsCacheDirty_ ) 405 regenerateFilteredItemsCache(); 406 nbLines = filteredItemsCache_.size(); 407 } 408 409 return nbLines; 410 } 411 412 // Implementation of the virtual function. 413 int LogFilteredData::doGetMaxLength() const 414 { 415 int max_length; 416 417 if ( visibility_ == MatchesOnly ) 418 max_length = maxLength_; 419 else if ( visibility_ == MarksOnly ) 420 max_length = maxLengthMarks_; 421 else 422 max_length = qMax( maxLength_, maxLengthMarks_ ); 423 424 return max_length; 425 } 426 427 // Implementation of the virtual function. 428 int LogFilteredData::doGetLineLength( qint64 lineNum ) const 429 { 430 qint64 line = findLogDataLine( lineNum ); 431 return sourceLogData_->getExpandedLineString( line ).length(); 432 } 433 434 void LogFilteredData::doSetDisplayEncoding( Encoding encoding ) 435 { 436 LOG(logDEBUG) << "AbstractLogData::setDisplayEncoding: " << static_cast<int>( encoding ); 437 } 438 439 void LogFilteredData::doSetMultibyteEncodingOffsets( int, int ) 440 { 441 } 442 443 // TODO: We might be a bit smarter and not regenerate the whole thing when 444 // e.g. stuff is added at the end of the search. 445 void LogFilteredData::regenerateFilteredItemsCache() const 446 { 447 LOG(logDEBUG) << "regenerateFilteredItemsCache"; 448 449 filteredItemsCache_.clear(); 450 filteredItemsCache_.reserve( matching_lines_.size() + marks_.size() ); 451 // (it's an overestimate but probably not by much so it's fine) 452 453 auto i = matching_lines_.cbegin(); 454 Marks::const_iterator j = marks_.begin(); 455 456 while ( ( i != matching_lines_.cend() ) || ( j != marks_.end() ) ) { 457 qint64 next_mark = 458 ( j != marks_.end() ) ? j->lineNumber() : std::numeric_limits<qint64>::max(); 459 qint64 next_match = 460 ( i != matching_lines_.cend() ) ? i->lineNumber() : std::numeric_limits<qint64>::max(); 461 // We choose a Mark over a Match if a line is both, just an arbitrary choice really. 462 if ( next_mark <= next_match ) { 463 // LOG(logDEBUG) << "Add mark at " << next_mark; 464 filteredItemsCache_.push_back( FilteredItem( next_mark, Mark ) ); 465 if ( j != marks_.end() ) 466 ++j; 467 if ( ( next_mark == next_match ) && ( i != matching_lines_.cend() ) ) 468 ++i; // Case when it's both match and mark. 469 } 470 else { 471 // LOG(logDEBUG) << "Add match at " << next_match; 472 filteredItemsCache_.push_back( FilteredItem( next_match, Match ) ); 473 if ( i != matching_lines_.cend() ) 474 ++i; 475 } 476 } 477 478 filteredItemsCacheDirty_ = false; 479 480 LOG(logDEBUG) << "finished regenerateFilteredItemsCache"; 481 } 482