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