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