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