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