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