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