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