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 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 void LogFilteredData::deleteMark( QChar mark ) 206 { 207 marks_.deleteMark( mark ); 208 filteredItemsCacheDirty_ = true; 209 210 // FIXME: maxLengthMarks_ 211 } 212 213 void LogFilteredData::deleteMark( qint64 line ) 214 { 215 marks_.deleteMark( line ); 216 filteredItemsCacheDirty_ = true; 217 218 // Now update the max length if needed 219 if ( sourceLogData_->getLineLength( line ) >= maxLengthMarks_ ) { 220 LOG(logDEBUG) << "deleteMark recalculating longest mark"; 221 maxLengthMarks_ = 0; 222 for ( Marks::const_iterator i = marks_.begin(); 223 i != marks_.end(); ++i ) { 224 LOG(logDEBUG) << "line " << i->lineNumber(); 225 maxLengthMarks_ = qMax( maxLengthMarks_, 226 sourceLogData_->getLineLength( i->lineNumber() ) ); 227 } 228 } 229 } 230 231 void LogFilteredData::clearMarks() 232 { 233 marks_.clear(); 234 filteredItemsCacheDirty_ = true; 235 maxLengthMarks_ = 0; 236 } 237 238 void LogFilteredData::setVisibility( Visibility visi ) 239 { 240 visibility_ = visi; 241 } 242 243 // 244 // Slots 245 // 246 void LogFilteredData::handleSearchProgressed( int nbMatches, int progress ) 247 { 248 LOG(logDEBUG) << "LogFilteredData::handleSearchProgressed matches=" 249 << nbMatches << " progress=" << progress; 250 251 // searchDone_ = true; 252 workerThread_.getSearchResult( &maxLength_, &matching_lines_, &nbLinesProcessed_ ); 253 filteredItemsCacheDirty_ = true; 254 255 emit searchProgressed( nbMatches, progress ); 256 } 257 258 LineNumber LogFilteredData::findLogDataLine( LineNumber lineNum ) const 259 { 260 LineNumber line = 0; 261 if ( visibility_ == MatchesOnly ) { 262 if ( lineNum < matching_lines_.size() ) { 263 line = matching_lines_[lineNum].lineNumber(); 264 } 265 else { 266 LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; 267 } 268 } 269 else if ( visibility_ == MarksOnly ) { 270 if ( lineNum < marks_.size() ) 271 line = marks_.getLineMarkedByIndex( lineNum ); 272 else 273 LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; 274 } 275 else { 276 // Regenerate the cache if needed 277 if ( filteredItemsCacheDirty_ ) 278 regenerateFilteredItemsCache(); 279 280 if ( lineNum < filteredItemsCache_.size() ) 281 line = filteredItemsCache_[ lineNum ].lineNumber(); 282 else 283 LOG(logERROR) << "Index too big in LogFilteredData: " << lineNum; 284 } 285 286 return line; 287 } 288 289 // Implementation of the virtual function. 290 QString LogFilteredData::doGetLineString( qint64 lineNum ) const 291 { 292 qint64 line = findLogDataLine( lineNum ); 293 294 QString string = sourceLogData_->getLineString( line ); 295 return string; 296 } 297 298 // Implementation of the virtual function. 299 QString LogFilteredData::doGetExpandedLineString( qint64 lineNum ) const 300 { 301 qint64 line = findLogDataLine( lineNum ); 302 303 QString string = sourceLogData_->getExpandedLineString( line ); 304 return string; 305 } 306 307 // Implementation of the virtual function. 308 QStringList LogFilteredData::doGetLines( qint64 first_line, int number ) const 309 { 310 QStringList list; 311 312 for ( int i = first_line; i < first_line + number; i++ ) { 313 list.append( doGetLineString( i ) ); 314 } 315 316 return list; 317 } 318 319 // Implementation of the virtual function. 320 QStringList LogFilteredData::doGetExpandedLines( qint64 first_line, int number ) const 321 { 322 QStringList list; 323 324 for ( int i = first_line; i < first_line + number; i++ ) { 325 list.append( doGetExpandedLineString( i ) ); 326 } 327 328 return list; 329 } 330 331 // Implementation of the virtual function. 332 qint64 LogFilteredData::doGetNbLine() const 333 { 334 qint64 nbLines; 335 336 if ( visibility_ == MatchesOnly ) 337 nbLines = matching_lines_.size(); 338 else if ( visibility_ == MarksOnly ) 339 nbLines = marks_.size(); 340 else { 341 // Regenerate the cache if needed (hopefully most of the time 342 // it won't be necessarily) 343 if ( filteredItemsCacheDirty_ ) 344 regenerateFilteredItemsCache(); 345 nbLines = filteredItemsCache_.size(); 346 } 347 348 return nbLines; 349 } 350 351 // Implementation of the virtual function. 352 int LogFilteredData::doGetMaxLength() const 353 { 354 int max_length; 355 356 if ( visibility_ == MatchesOnly ) 357 max_length = maxLength_; 358 else if ( visibility_ == MarksOnly ) 359 max_length = maxLengthMarks_; 360 else 361 max_length = qMax( maxLength_, maxLengthMarks_ ); 362 363 return max_length; 364 } 365 366 // Implementation of the virtual function. 367 int LogFilteredData::doGetLineLength( qint64 lineNum ) const 368 { 369 qint64 line = findLogDataLine( lineNum ); 370 return sourceLogData_->getExpandedLineString( line ).length(); 371 } 372 373 // TODO: We might be a bit smarter and not regenerate the whole thing when 374 // e.g. stuff is added at the end of the search. 375 void LogFilteredData::regenerateFilteredItemsCache() const 376 { 377 LOG(logDEBUG) << "regenerateFilteredItemsCache"; 378 379 filteredItemsCache_.clear(); 380 filteredItemsCache_.reserve( matching_lines_.size() + marks_.size() ); 381 // (it's an overestimate but probably not by much so it's fine) 382 383 auto i = matching_lines_.cbegin(); 384 Marks::const_iterator j = marks_.begin(); 385 386 while ( ( i != matching_lines_.cend() ) || ( j != marks_.end() ) ) { 387 qint64 next_mark = 388 ( j != marks_.end() ) ? j->lineNumber() : std::numeric_limits<qint64>::max(); 389 qint64 next_match = 390 ( i != matching_lines_.cend() ) ? i->lineNumber() : std::numeric_limits<qint64>::max(); 391 // We choose a Mark over a Match if a line is both, just an arbitrary choice really. 392 if ( next_mark <= next_match ) { 393 // LOG(logDEBUG) << "Add mark at " << next_mark; 394 filteredItemsCache_.push_back( FilteredItem( next_mark, Mark ) ); 395 if ( j != marks_.end() ) 396 ++j; 397 if ( ( next_mark == next_match ) && ( i != matching_lines_.cend() ) ) 398 ++i; // Case when it's both match and mark. 399 } 400 else { 401 // LOG(logDEBUG) << "Add match at " << next_match; 402 filteredItemsCache_.push_back( FilteredItem( next_match, Match ) ); 403 if ( i != matching_lines_.cend() ) 404 ++i; 405 } 406 } 407 408 filteredItemsCacheDirty_ = false; 409 410 LOG(logDEBUG) << "finished regenerateFilteredItemsCache"; 411 } 412