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