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