1 /* 2 * Copyright (C) 2009, 2010, 2013, 2014 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 LogData, the content of a log file. 21 22 #include <iostream> 23 24 #include <cassert> 25 26 #include <QFileInfo> 27 28 #include "log.h" 29 30 #include "logdata.h" 31 #include "logfiltereddata.h" 32 33 // Implementation of the 'start' functions for each operation 34 35 void LogData::AttachOperation::doStart( 36 LogDataWorkerThread& workerThread ) const 37 { 38 LOG(logDEBUG) << "Attaching " << filename_.toStdString(); 39 workerThread.attachFile( filename_ ); 40 workerThread.indexAll(); 41 } 42 43 void LogData::FullIndexOperation::doStart( 44 LogDataWorkerThread& workerThread ) const 45 { 46 LOG(logDEBUG) << "Reindexing (full)"; 47 workerThread.indexAll(); 48 } 49 50 void LogData::PartialIndexOperation::doStart( 51 LogDataWorkerThread& workerThread ) const 52 { 53 LOG(logDEBUG) << "Reindexing (partial)"; 54 workerThread.indexAdditionalLines( filesize_ ); 55 } 56 57 58 // Constructs an empty log file. 59 // It must be displayed without error. 60 LogData::LogData() : AbstractLogData(), fileWatcher_(), linePosition_(), 61 fileMutex_(), dataMutex_(), workerThread_() 62 { 63 // Start with an "empty" log 64 file_ = nullptr; 65 fileSize_ = 0; 66 nbLines_ = 0; 67 maxLength_ = 0; 68 currentOperation_ = nullptr; 69 nextOperation_ = nullptr; 70 71 // Initialise the file watcher 72 connect( &fileWatcher_, SIGNAL( fileChanged( const QString& ) ), 73 this, SLOT( fileChangedOnDisk() ) ); 74 // Forward the update signal 75 connect( &workerThread_, SIGNAL( indexingProgressed( int ) ), 76 this, SIGNAL( loadingProgressed( int ) ) ); 77 connect( &workerThread_, SIGNAL( indexingFinished( LoadingStatus ) ), 78 this, SLOT( indexingFinished( LoadingStatus ) ) ); 79 80 // Starts the worker thread 81 workerThread_.start(); 82 } 83 84 LogData::~LogData() 85 { 86 // FIXME 87 // workerThread_.stop(); 88 } 89 90 // 91 // Public functions 92 // 93 94 void LogData::attachFile( const QString& fileName ) 95 { 96 LOG(logDEBUG) << "LogData::attachFile " << fileName.toStdString(); 97 98 if ( file_ ) { 99 // Remove the current file from the watch list 100 fileWatcher_.removeFile( file_->fileName() ); 101 } 102 103 workerThread_.interrupt(); 104 105 // If an attach operation is already in progress, the new one will 106 // be delayed until the current one is finished (canceled) 107 std::shared_ptr<const LogDataOperation> operation( new AttachOperation( fileName ) ); 108 enqueueOperation( std::move( operation ) ); 109 } 110 111 void LogData::interruptLoading() 112 { 113 workerThread_.interrupt(); 114 } 115 116 qint64 LogData::getFileSize() const 117 { 118 return fileSize_; 119 } 120 121 QDateTime LogData::getLastModifiedDate() const 122 { 123 return lastModifiedDate_; 124 } 125 126 // Return an initialised LogFilteredData. The search is not started. 127 LogFilteredData* LogData::getNewFilteredData() const 128 { 129 LogFilteredData* newFilteredData = new LogFilteredData( this ); 130 131 return newFilteredData; 132 } 133 134 void LogData::reload() 135 { 136 workerThread_.interrupt(); 137 138 enqueueOperation( std::make_shared<FullIndexOperation>() ); 139 } 140 141 // 142 // Private functions 143 // 144 145 // Add an operation to the queue and perform it immediately if 146 // there is none ongoing. 147 void LogData::enqueueOperation( std::shared_ptr<const LogDataOperation> new_operation ) 148 { 149 if ( currentOperation_ == nullptr ) 150 { 151 // We do it immediately 152 currentOperation_ = new_operation; 153 startOperation(); 154 } 155 else 156 { 157 // An operation is in progress... 158 // ... we schedule the attach op for later 159 nextOperation_ = new_operation; 160 } 161 } 162 163 // Performs the current operation asynchronously, a indexingFinished 164 // signal will be received when it's finished. 165 void LogData::startOperation() 166 { 167 if ( currentOperation_ ) 168 { 169 LOG(logDEBUG) << "startOperation found something to do."; 170 171 // If it's a full indexing ... 172 // ... we invalidate the non indexed data 173 if ( currentOperation_->isFull() ) { 174 fileSize_ = 0; 175 nbLines_ = 0; 176 maxLength_ = 0; 177 } 178 179 // And let the operation do its stuff 180 currentOperation_->start( workerThread_ ); 181 } 182 } 183 184 // 185 // Slots 186 // 187 188 void LogData::fileChangedOnDisk() 189 { 190 LOG(logDEBUG) << "signalFileChanged"; 191 192 fileWatcher_.removeFile( file_->fileName() ); 193 194 const QString name = file_->fileName(); 195 QFileInfo info( name ); 196 197 std::shared_ptr<LogDataOperation> newOperation; 198 199 LOG(logDEBUG) << "current fileSize=" << fileSize_; 200 LOG(logDEBUG) << "info file_->size()=" << info.size(); 201 if ( info.size() < fileSize_ ) { 202 fileChangedOnDisk_ = Truncated; 203 LOG(logINFO) << "File truncated"; 204 newOperation = std::make_shared<FullIndexOperation>(); 205 } 206 else if ( fileChangedOnDisk_ != DataAdded ) { 207 fileChangedOnDisk_ = DataAdded; 208 LOG(logINFO) << "New data on disk"; 209 newOperation = std::make_shared<PartialIndexOperation>( fileSize_ ); 210 } 211 212 if ( newOperation ) 213 enqueueOperation( newOperation ); 214 215 lastModifiedDate_ = info.lastModified(); 216 217 emit fileChanged( fileChangedOnDisk_ ); 218 // TODO: fileChangedOnDisk_, fileSize_ 219 } 220 221 void LogData::indexingFinished( LoadingStatus status ) 222 { 223 LOG(logDEBUG) << "Entering LogData::indexingFinished."; 224 225 // We use the newly created file data or restore the old ones. 226 // (Qt implicit copy makes this fast!) 227 { 228 QMutexLocker locker( &dataMutex_ ); 229 workerThread_.getIndexingData( &fileSize_, &maxLength_, &linePosition_ ); 230 nbLines_ = linePosition_.size(); 231 } 232 233 LOG(logDEBUG) << "indexingFinished: " << 234 ( status == LoadingStatus::Successful ) << 235 ", found " << nbLines_ << " lines."; 236 237 if ( status == LoadingStatus::Successful ) { 238 // Use the new filename if needed 239 if ( !currentOperation_->getFilename().isNull() ) { 240 QString newFileName = currentOperation_->getFilename(); 241 242 if ( file_ ) { 243 QMutexLocker locker( &fileMutex_ ); 244 file_->setFileName( newFileName ); 245 } 246 else { 247 QMutexLocker locker( &fileMutex_ ); 248 file_.reset( new QFile( newFileName ) ); 249 } 250 } 251 252 // Update the modified date/time if the file exists 253 lastModifiedDate_ = QDateTime(); 254 QFileInfo fileInfo( *file_ ); 255 if ( fileInfo.exists() ) 256 lastModifiedDate_ = fileInfo.lastModified(); 257 } 258 259 if ( file_ ) { 260 // And we watch the file for updates 261 fileChangedOnDisk_ = Unchanged; 262 fileWatcher_.addFile( file_->fileName() ); 263 } 264 265 emit loadingFinished( status ); 266 267 // So now the operation is done, let's see if there is something 268 // else to do, in which case, do it! 269 assert( currentOperation_ ); 270 271 currentOperation_ = std::move( nextOperation_ ); 272 nextOperation_.reset(); 273 274 if ( currentOperation_ ) { 275 LOG(logDEBUG) << "indexingFinished is performing the next operation"; 276 startOperation(); 277 } 278 } 279 280 // 281 // Implementation of virtual functions 282 // 283 qint64 LogData::doGetNbLine() const 284 { 285 return nbLines_; 286 } 287 288 int LogData::doGetMaxLength() const 289 { 290 return maxLength_; 291 } 292 293 int LogData::doGetLineLength( qint64 line ) const 294 { 295 if ( line >= nbLines_ ) { return 0; /* exception? */ } 296 297 int length = doGetExpandedLineString( line ).length(); 298 299 return length; 300 } 301 302 QString LogData::doGetLineString( qint64 line ) const 303 { 304 if ( line >= nbLines_ ) { return QString(); /* exception? */ } 305 306 dataMutex_.lock(); 307 fileMutex_.lock(); 308 file_->open( QIODevice::ReadOnly ); 309 310 file_->seek( (line == 0) ? 0 : linePosition_[line-1] ); 311 312 QString string = QString( file_->readLine() ); 313 314 file_->close(); 315 fileMutex_.unlock(); 316 dataMutex_.unlock(); 317 318 string.chop( 1 ); 319 320 return string; 321 } 322 323 QString LogData::doGetExpandedLineString( qint64 line ) const 324 { 325 if ( line >= nbLines_ ) { return QString(); /* exception? */ } 326 327 dataMutex_.lock(); 328 fileMutex_.lock(); 329 file_->open( QIODevice::ReadOnly ); 330 331 file_->seek( (line == 0) ? 0 : linePosition_[line-1] ); 332 333 QByteArray rawString = file_->readLine(); 334 335 file_->close(); 336 fileMutex_.unlock(); 337 dataMutex_.unlock(); 338 339 QString string = QString( untabify( rawString.constData() ) ); 340 string.chop( 1 ); 341 342 return string; 343 } 344 345 // Note this function is also called from the LogFilteredDataWorker thread, so 346 // data must be protected because they are changed in the main thread (by 347 // indexingFinished). 348 QStringList LogData::doGetLines( qint64 first_line, int number ) const 349 { 350 QStringList list; 351 const qint64 last_line = first_line + number - 1; 352 353 // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number; 354 355 if ( number == 0 ) { 356 return QStringList(); 357 } 358 359 if ( last_line >= nbLines_ ) { 360 LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for"; 361 return QStringList(); /* exception? */ 362 } 363 364 dataMutex_.lock(); 365 366 fileMutex_.lock(); 367 file_->open( QIODevice::ReadOnly ); 368 369 const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1]; 370 const qint64 last_byte = linePosition_[last_line]; 371 // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " last_byte:" << last_byte; 372 file_->seek( first_byte ); 373 QByteArray blob = file_->read( last_byte - first_byte ); 374 375 file_->close(); 376 fileMutex_.unlock(); 377 378 qint64 beginning = 0; 379 qint64 end = 0; 380 for ( qint64 line = first_line; (line <= last_line); line++ ) { 381 end = linePosition_[line] - first_byte; 382 // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end; 383 QByteArray this_line = blob.mid( beginning, end - beginning - 1 ); 384 // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString(); 385 list.append( QString( this_line ) ); 386 beginning = end; 387 } 388 389 dataMutex_.unlock(); 390 391 return list; 392 } 393 394 QStringList LogData::doGetExpandedLines( qint64 first_line, int number ) const 395 { 396 QStringList list; 397 const qint64 last_line = first_line + number - 1; 398 399 if ( number == 0 ) { 400 return QStringList(); 401 } 402 403 if ( last_line >= nbLines_ ) { 404 LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for"; 405 return QStringList(); /* exception? */ 406 } 407 408 dataMutex_.lock(); 409 410 fileMutex_.lock(); 411 file_->open( QIODevice::ReadOnly ); 412 413 const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1]; 414 const qint64 last_byte = linePosition_[last_line]; 415 // LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " last_byte:" << last_byte; 416 file_->seek( first_byte ); 417 QByteArray blob = file_->read( last_byte - first_byte ); 418 419 file_->close(); 420 fileMutex_.unlock(); 421 422 qint64 beginning = 0; 423 qint64 end = 0; 424 for ( qint64 line = first_line; (line <= last_line); line++ ) { 425 end = linePosition_[line] - first_byte; 426 // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end; 427 QByteArray this_line = blob.mid( beginning, end - beginning - 1 ); 428 // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString(); 429 list.append( untabify( this_line.constData() ) ); 430 beginning = end; 431 } 432 433 dataMutex_.unlock(); 434 435 return list; 436 } 437