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 // 153 // Private functions 154 // 155 156 // Add an operation to the queue and perform it immediately if 157 // there is none ongoing. 158 void LogData::enqueueOperation( std::shared_ptr<const LogDataOperation> new_operation ) 159 { 160 if ( currentOperation_ == nullptr ) 161 { 162 // We do it immediately 163 currentOperation_ = new_operation; 164 startOperation(); 165 } 166 else 167 { 168 // An operation is in progress... 169 // ... we schedule the attach op for later 170 nextOperation_ = new_operation; 171 } 172 } 173 174 // Performs the current operation asynchronously, a indexingFinished 175 // signal will be received when it's finished. 176 void LogData::startOperation() 177 { 178 if ( currentOperation_ ) 179 { 180 LOG(logDEBUG) << "startOperation found something to do."; 181 182 // Let the operation do its stuff 183 currentOperation_->start( workerThread_ ); 184 } 185 } 186 187 // 188 // Slots 189 // 190 191 void LogData::fileChangedOnDisk() 192 { 193 LOG(logDEBUG) << "signalFileChanged"; 194 195 const QString name = attached_file_->fileName(); 196 QFileInfo info( name ); 197 198 std::shared_ptr<LogDataOperation> newOperation; 199 200 qint64 file_size = indexing_data_.getSize(); 201 LOG(logDEBUG) << "current fileSize=" << file_size; 202 LOG(logDEBUG) << "info file_->size()=" << info.size(); 203 if ( info.size() < file_size ) { 204 fileChangedOnDisk_ = Truncated; 205 LOG(logINFO) << "File truncated"; 206 newOperation = std::make_shared<FullIndexOperation>(); 207 } 208 else if ( fileChangedOnDisk_ != DataAdded ) { 209 fileChangedOnDisk_ = DataAdded; 210 LOG(logINFO) << "New data on disk"; 211 newOperation = std::make_shared<PartialIndexOperation>( file_size ); 212 } 213 214 if ( newOperation ) 215 enqueueOperation( newOperation ); 216 217 lastModifiedDate_ = info.lastModified(); 218 219 emit fileChanged( fileChangedOnDisk_ ); 220 // TODO: fileChangedOnDisk_, fileSize_ 221 } 222 223 void LogData::indexingFinished( LoadingStatus status ) 224 { 225 LOG(logDEBUG) << "indexingFinished: " << 226 ( status == LoadingStatus::Successful ) << 227 ", found " << indexing_data_.getNbLines() << " lines."; 228 229 if ( status == LoadingStatus::Successful ) { 230 // Start watching we watch the file for updates 231 fileChangedOnDisk_ = Unchanged; 232 fileWatcher_->addFile( attached_file_->fileName() ); 233 234 // Update the modified date/time if the file exists 235 lastModifiedDate_ = QDateTime(); 236 QFileInfo fileInfo( *attached_file_ ); 237 if ( fileInfo.exists() ) 238 lastModifiedDate_ = fileInfo.lastModified(); 239 } 240 241 // FIXME be cleverer here as a notification might have arrived whilst we 242 // were indexing. 243 fileChangedOnDisk_ = Unchanged; 244 245 LOG(logDEBUG) << "Sending indexingFinished."; 246 emit loadingFinished( status ); 247 248 // So now the operation is done, let's see if there is something 249 // else to do, in which case, do it! 250 assert( currentOperation_ ); 251 252 currentOperation_ = std::move( nextOperation_ ); 253 nextOperation_.reset(); 254 255 if ( currentOperation_ ) { 256 LOG(logDEBUG) << "indexingFinished is performing the next operation"; 257 startOperation(); 258 } 259 } 260 261 // 262 // Implementation of virtual functions 263 // 264 qint64 LogData::doGetNbLine() const 265 { 266 return indexing_data_.getNbLines(); 267 } 268 269 int LogData::doGetMaxLength() const 270 { 271 return indexing_data_.getMaxLength(); 272 } 273 274 int LogData::doGetLineLength( qint64 line ) const 275 { 276 if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ } 277 278 int length = doGetExpandedLineString( line ).length(); 279 280 return length; 281 } 282 283 QString LogData::doGetLineString( qint64 line ) const 284 { 285 if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ } 286 287 fileMutex_.lock(); 288 289 attached_file_->seek( (line == 0) ? 0 : indexing_data_.getPosForLine( line-1 ) ); 290 291 QString string = QString( attached_file_->readLine() ); 292 293 fileMutex_.unlock(); 294 295 string.chop( 1 ); 296 297 return string; 298 } 299 300 QString LogData::doGetExpandedLineString( qint64 line ) const 301 { 302 if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ } 303 304 fileMutex_.lock(); 305 306 attached_file_->seek( (line == 0) ? 0 : indexing_data_.getPosForLine( line-1 ) ); 307 308 QByteArray rawString = attached_file_->readLine(); 309 310 fileMutex_.unlock(); 311 312 QString string = QString( untabify( rawString.constData() ) ); 313 string.chop( 1 ); 314 315 return string; 316 } 317 318 // Note this function is also called from the LogFilteredDataWorker thread, so 319 // data must be protected because they are changed in the main thread (by 320 // indexingFinished). 321 QStringList LogData::doGetLines( qint64 first_line, int number ) const 322 { 323 QStringList list; 324 const qint64 last_line = first_line + number - 1; 325 326 // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number; 327 328 if ( number == 0 ) { 329 return QStringList(); 330 } 331 332 if ( last_line >= indexing_data_.getNbLines() ) { 333 LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for"; 334 return QStringList(); /* exception? */ 335 } 336 337 fileMutex_.lock(); 338 339 const qint64 first_byte = (first_line == 0) ? 340 0 : indexing_data_.getPosForLine( first_line-1 ); 341 const qint64 last_byte = indexing_data_.getPosForLine( last_line ); 342 // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " last_byte:" << last_byte; 343 attached_file_->seek( first_byte ); 344 QByteArray blob = attached_file_->read( last_byte - first_byte ); 345 346 fileMutex_.unlock(); 347 348 qint64 beginning = 0; 349 qint64 end = 0; 350 for ( qint64 line = first_line; (line <= last_line); line++ ) { 351 end = indexing_data_.getPosForLine( line ) - first_byte; 352 // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end; 353 QByteArray this_line = blob.mid( beginning, end - beginning - 1 ); 354 // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString(); 355 list.append( QString( this_line ) ); 356 beginning = end; 357 } 358 359 return list; 360 } 361 362 QStringList LogData::doGetExpandedLines( qint64 first_line, int number ) const 363 { 364 QStringList list; 365 const qint64 last_line = first_line + number - 1; 366 367 if ( number == 0 ) { 368 return QStringList(); 369 } 370 371 if ( last_line >= indexing_data_.getNbLines() ) { 372 LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for"; 373 return QStringList(); /* exception? */ 374 } 375 376 fileMutex_.lock(); 377 378 const qint64 first_byte = (first_line == 0) ? 379 0 : indexing_data_.getPosForLine( first_line-1 ); 380 const qint64 last_byte = indexing_data_.getPosForLine( last_line ); 381 // LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " last_byte:" << last_byte; 382 attached_file_->seek( first_byte ); 383 QByteArray blob = attached_file_->read( last_byte - first_byte ); 384 385 fileMutex_.unlock(); 386 387 qint64 beginning = 0; 388 qint64 end = 0; 389 for ( qint64 line = first_line; (line <= last_line); line++ ) { 390 end = indexing_data_.getPosForLine( line ) - first_byte; 391 // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end; 392 QByteArray this_line = blob.mid( beginning, end - beginning - 1 ); 393 // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString(); 394 list.append( untabify( this_line.constData() ) ); 395 beginning = end; 396 } 397 398 return list; 399 } 400