xref: /glogg/src/data/logdata.cpp (revision 84af0c9b75fb9369ab66df476dd881cd6d30efcf)
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 #ifdef GLOGG_SUPPORTS_INOTIFY
33 #include "inotifyfilewatcher.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     file_         = nullptr;
70     fileSize_     = 0;
71     nbLines_      = 0;
72     maxLength_    = 0;
73     currentOperation_ = nullptr;
74     nextOperation_    = nullptr;
75 
76 #ifdef GLOGG_SUPPORTS_INOTIFY
77     fileWatcher_ = std::make_shared<INotifyFileWatcher>();
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     // FIXME
98     // workerThread_.stop();
99 }
100 
101 //
102 // Public functions
103 //
104 
105 void LogData::attachFile( const QString& fileName )
106 {
107     LOG(logDEBUG) << "LogData::attachFile " << fileName.toStdString();
108 
109     if ( file_ ) {
110         // Remove the current file from the watch list
111         fileWatcher_->removeFile( file_->fileName() );
112     }
113 
114     workerThread_.interrupt();
115 
116     // If an attach operation is already in progress, the new one will
117     // be delayed until the current one is finished (canceled)
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 fileSize_;
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         // If it's a full indexing ...
183         // ... we invalidate the non indexed data
184         if ( currentOperation_->isFull() ) {
185             fileSize_     = 0;
186             nbLines_      = 0;
187             maxLength_    = 0;
188         }
189 
190         // And let the operation do its stuff
191         currentOperation_->start( workerThread_ );
192     }
193 }
194 
195 //
196 // Slots
197 //
198 
199 void LogData::fileChangedOnDisk()
200 {
201     LOG(logDEBUG) << "signalFileChanged";
202 
203     fileWatcher_->removeFile( file_->fileName() );
204 
205     const QString name = file_->fileName();
206     QFileInfo info( name );
207 
208     std::shared_ptr<LogDataOperation> newOperation;
209 
210     LOG(logDEBUG) << "current fileSize=" << fileSize_;
211     LOG(logDEBUG) << "info file_->size()=" << info.size();
212     if ( info.size() < fileSize_ ) {
213         fileChangedOnDisk_ = Truncated;
214         LOG(logINFO) << "File truncated";
215         newOperation = std::make_shared<FullIndexOperation>();
216     }
217     else if ( fileChangedOnDisk_ != DataAdded ) {
218         fileChangedOnDisk_ = DataAdded;
219         LOG(logINFO) << "New data on disk";
220         newOperation = std::make_shared<PartialIndexOperation>( fileSize_ );
221     }
222 
223     if ( newOperation )
224         enqueueOperation( newOperation );
225 
226     lastModifiedDate_ = info.lastModified();
227 
228     emit fileChanged( fileChangedOnDisk_ );
229     // TODO: fileChangedOnDisk_, fileSize_
230 }
231 
232 void LogData::indexingFinished( LoadingStatus status )
233 {
234     LOG(logDEBUG) << "Entering LogData::indexingFinished.";
235 
236     // We use the newly created file data or restore the old ones.
237     // (Qt implicit copy makes this fast!)
238     {
239         QMutexLocker locker( &dataMutex_ );
240         workerThread_.getIndexingData( &fileSize_, &maxLength_, &linePosition_ );
241         nbLines_ = linePosition_.size();
242     }
243 
244     LOG(logDEBUG) << "indexingFinished: " <<
245         ( status == LoadingStatus::Successful ) <<
246         ", found " << nbLines_ << " lines.";
247 
248     if ( status == LoadingStatus::Successful ) {
249         // Use the new filename if needed
250         if ( !currentOperation_->getFilename().isNull() ) {
251             QString newFileName = currentOperation_->getFilename();
252 
253             if ( file_ ) {
254                 QMutexLocker locker( &fileMutex_ );
255                 file_->setFileName( newFileName );
256             }
257             else {
258                 QMutexLocker locker( &fileMutex_ );
259                 file_.reset( new QFile( newFileName ) );
260             }
261         }
262 
263         // Update the modified date/time if the file exists
264         lastModifiedDate_ = QDateTime();
265         QFileInfo fileInfo( *file_ );
266         if ( fileInfo.exists() )
267             lastModifiedDate_ = fileInfo.lastModified();
268     }
269 
270     if ( file_ ) {
271         // And we watch the file for updates
272         fileChangedOnDisk_ = Unchanged;
273         fileWatcher_->addFile( file_->fileName() );
274     }
275 
276     emit loadingFinished( status );
277 
278     // So now the operation is done, let's see if there is something
279     // else to do, in which case, do it!
280     assert( currentOperation_ );
281 
282     currentOperation_ = std::move( nextOperation_ );
283     nextOperation_.reset();
284 
285     if ( currentOperation_ ) {
286         LOG(logDEBUG) << "indexingFinished is performing the next operation";
287         startOperation();
288     }
289 }
290 
291 //
292 // Implementation of virtual functions
293 //
294 qint64 LogData::doGetNbLine() const
295 {
296     return nbLines_;
297 }
298 
299 int LogData::doGetMaxLength() const
300 {
301     return maxLength_;
302 }
303 
304 int LogData::doGetLineLength( qint64 line ) const
305 {
306     if ( line >= nbLines_ ) { return 0; /* exception? */ }
307 
308     int length = doGetExpandedLineString( line ).length();
309 
310     return length;
311 }
312 
313 QString LogData::doGetLineString( qint64 line ) const
314 {
315     if ( line >= nbLines_ ) { return QString(); /* exception? */ }
316 
317     dataMutex_.lock();
318     fileMutex_.lock();
319     file_->open( QIODevice::ReadOnly );
320 
321     file_->seek( (line == 0) ? 0 : linePosition_[line-1] );
322 
323     QString string = QString( file_->readLine() );
324 
325     file_->close();
326     fileMutex_.unlock();
327     dataMutex_.unlock();
328 
329     string.chop( 1 );
330 
331     return string;
332 }
333 
334 QString LogData::doGetExpandedLineString( qint64 line ) const
335 {
336     if ( line >= nbLines_ ) { return QString(); /* exception? */ }
337 
338     dataMutex_.lock();
339     fileMutex_.lock();
340     file_->open( QIODevice::ReadOnly );
341 
342     file_->seek( (line == 0) ? 0 : linePosition_[line-1] );
343 
344     QByteArray rawString = file_->readLine();
345 
346     file_->close();
347     fileMutex_.unlock();
348     dataMutex_.unlock();
349 
350     QString string = QString( untabify( rawString.constData() ) );
351     string.chop( 1 );
352 
353     return string;
354 }
355 
356 // Note this function is also called from the LogFilteredDataWorker thread, so
357 // data must be protected because they are changed in the main thread (by
358 // indexingFinished).
359 QStringList LogData::doGetLines( qint64 first_line, int number ) const
360 {
361     QStringList list;
362     const qint64 last_line = first_line + number - 1;
363 
364     // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number;
365 
366     if ( number == 0 ) {
367         return QStringList();
368     }
369 
370     if ( last_line >= nbLines_ ) {
371         LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for";
372         return QStringList(); /* exception? */
373     }
374 
375     dataMutex_.lock();
376 
377     fileMutex_.lock();
378     file_->open( QIODevice::ReadOnly );
379 
380     const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1];
381     const qint64 last_byte  = linePosition_[last_line];
382     // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " last_byte:" << last_byte;
383     file_->seek( first_byte );
384     QByteArray blob = file_->read( last_byte - first_byte );
385 
386     file_->close();
387     fileMutex_.unlock();
388 
389     qint64 beginning = 0;
390     qint64 end = 0;
391     for ( qint64 line = first_line; (line <= last_line); line++ ) {
392         end = linePosition_[line] - first_byte;
393         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
394         QByteArray this_line = blob.mid( beginning, end - beginning - 1 );
395         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
396         list.append( QString( this_line ) );
397         beginning = end;
398     }
399 
400     dataMutex_.unlock();
401 
402     return list;
403 }
404 
405 QStringList LogData::doGetExpandedLines( qint64 first_line, int number ) const
406 {
407     QStringList list;
408     const qint64 last_line = first_line + number - 1;
409 
410     if ( number == 0 ) {
411         return QStringList();
412     }
413 
414     if ( last_line >= nbLines_ ) {
415         LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for";
416         return QStringList(); /* exception? */
417     }
418 
419     dataMutex_.lock();
420 
421     fileMutex_.lock();
422     file_->open( QIODevice::ReadOnly );
423 
424     const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1];
425     const qint64 last_byte  = linePosition_[last_line];
426     // LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " last_byte:" << last_byte;
427     file_->seek( first_byte );
428     QByteArray blob = file_->read( last_byte - first_byte );
429 
430     file_->close();
431     fileMutex_.unlock();
432 
433     qint64 beginning = 0;
434     qint64 end = 0;
435     for ( qint64 line = first_line; (line <= last_line); line++ ) {
436         end = linePosition_[line] - first_byte;
437         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
438         QByteArray this_line = blob.mid( beginning, end - beginning - 1 );
439         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
440         list.append( untabify( this_line.constData() ) );
441         beginning = end;
442     }
443 
444     dataMutex_.unlock();
445 
446     return list;
447 }
448