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 // 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( bool success ) 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: " << success << 234 ", found " << nbLines_ << " lines."; 235 236 if ( success ) { 237 // Use the new filename if needed 238 if ( !currentOperation_->getFilename().isNull() ) { 239 QString newFileName = currentOperation_->getFilename(); 240 241 if ( file_ ) { 242 QMutexLocker locker( &fileMutex_ ); 243 file_->setFileName( newFileName ); 244 } 245 else { 246 QMutexLocker locker( &fileMutex_ ); 247 file_.reset( new QFile( newFileName ) ); 248 } 249 } 250 251 // Update the modified date/time if the file exists 252 lastModifiedDate_ = QDateTime(); 253 QFileInfo fileInfo( *file_ ); 254 if ( fileInfo.exists() ) 255 lastModifiedDate_ = fileInfo.lastModified(); 256 } 257 258 if ( file_ ) { 259 // And we watch the file for updates 260 fileChangedOnDisk_ = Unchanged; 261 fileWatcher_.addFile( file_->fileName() ); 262 } 263 264 emit loadingFinished( success ); 265 266 // So now the operation is done, let's see if there is something 267 // else to do, in which case, do it! 268 assert( currentOperation_ ); 269 270 currentOperation_ = std::move( nextOperation_ ); 271 nextOperation_.reset(); 272 273 if ( currentOperation_ ) { 274 LOG(logDEBUG) << "indexingFinished is performing the next operation"; 275 startOperation(); 276 } 277 } 278 279 // 280 // Implementation of virtual functions 281 // 282 qint64 LogData::doGetNbLine() const 283 { 284 return nbLines_; 285 } 286 287 int LogData::doGetMaxLength() const 288 { 289 return maxLength_; 290 } 291 292 int LogData::doGetLineLength( qint64 line ) const 293 { 294 if ( line >= nbLines_ ) { return 0; /* exception? */ } 295 296 int length = doGetExpandedLineString( line ).length(); 297 298 return length; 299 } 300 301 QString LogData::doGetLineString( qint64 line ) const 302 { 303 if ( line >= nbLines_ ) { return QString(); /* exception? */ } 304 305 dataMutex_.lock(); 306 fileMutex_.lock(); 307 file_->open( QIODevice::ReadOnly ); 308 309 file_->seek( (line == 0) ? 0 : linePosition_[line-1] ); 310 311 QString string = QString( file_->readLine() ); 312 313 file_->close(); 314 fileMutex_.unlock(); 315 dataMutex_.unlock(); 316 317 string.chop( 1 ); 318 319 return string; 320 } 321 322 QString LogData::doGetExpandedLineString( qint64 line ) const 323 { 324 if ( line >= nbLines_ ) { return QString(); /* exception? */ } 325 326 dataMutex_.lock(); 327 fileMutex_.lock(); 328 file_->open( QIODevice::ReadOnly ); 329 330 file_->seek( (line == 0) ? 0 : linePosition_[line-1] ); 331 332 QByteArray rawString = file_->readLine(); 333 334 file_->close(); 335 fileMutex_.unlock(); 336 dataMutex_.unlock(); 337 338 QString string = QString( untabify( rawString.constData() ) ); 339 string.chop( 1 ); 340 341 return string; 342 } 343 344 // Note this function is also called from the LogFilteredDataWorker thread, so 345 // data must be protected because they are changed in the main thread (by 346 // indexingFinished). 347 QStringList LogData::doGetLines( qint64 first_line, int number ) const 348 { 349 QStringList list; 350 const qint64 last_line = first_line + number - 1; 351 352 // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number; 353 354 if ( number == 0 ) { 355 return QStringList(); 356 } 357 358 if ( last_line >= nbLines_ ) { 359 LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for"; 360 return QStringList(); /* exception? */ 361 } 362 363 dataMutex_.lock(); 364 365 fileMutex_.lock(); 366 file_->open( QIODevice::ReadOnly ); 367 368 const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1]; 369 const qint64 last_byte = linePosition_[last_line]; 370 // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " last_byte:" << last_byte; 371 file_->seek( first_byte ); 372 QByteArray blob = file_->read( last_byte - first_byte ); 373 374 file_->close(); 375 fileMutex_.unlock(); 376 377 qint64 beginning = 0; 378 qint64 end = 0; 379 for ( qint64 line = first_line; (line <= last_line); line++ ) { 380 end = linePosition_[line] - first_byte; 381 // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end; 382 QByteArray this_line = blob.mid( beginning, end - beginning - 1 ); 383 // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString(); 384 list.append( QString( this_line ) ); 385 beginning = end; 386 } 387 388 dataMutex_.unlock(); 389 390 return list; 391 } 392 393 QStringList LogData::doGetExpandedLines( qint64 first_line, int number ) const 394 { 395 QStringList list; 396 const qint64 last_line = first_line + number - 1; 397 398 if ( number == 0 ) { 399 return QStringList(); 400 } 401 402 if ( last_line >= nbLines_ ) { 403 LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for"; 404 return QStringList(); /* exception? */ 405 } 406 407 dataMutex_.lock(); 408 409 fileMutex_.lock(); 410 file_->open( QIODevice::ReadOnly ); 411 412 const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1]; 413 const qint64 last_byte = linePosition_[last_line]; 414 // LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " last_byte:" << last_byte; 415 file_->seek( first_byte ); 416 QByteArray blob = file_->read( last_byte - first_byte ); 417 418 file_->close(); 419 fileMutex_.unlock(); 420 421 qint64 beginning = 0; 422 qint64 end = 0; 423 for ( qint64 line = first_line; (line <= last_line); line++ ) { 424 end = linePosition_[line] - first_byte; 425 // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end; 426 QByteArray this_line = blob.mid( beginning, end - beginning - 1 ); 427 // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString(); 428 list.append( untabify( this_line.constData() ) ); 429 beginning = end; 430 } 431 432 dataMutex_.unlock(); 433 434 return list; 435 } 436