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