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