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