xref: /glogg/src/data/logdata.cpp (revision f8bd90d80f96eed0ec6023963fb05cb207f18cd3)
1 /*
2  * Copyright (C) 2009, 2010, 2013 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 
33 // Implementation of the 'start' functions for each operation
34 
35 void LogData::AttachOperation::doStart(
36         LogDataWorkerThread& workerThread ) const
37 {
38     LOG(logDEBUG) << "Attaching " << filename_.toStdString();
39     workerThread.attachFile( filename_ );
40     workerThread.indexAll();
41 }
42 
43 void LogData::FullIndexOperation::doStart(
44         LogDataWorkerThread& workerThread ) const
45 {
46     LOG(logDEBUG) << "Reindexing (full)";
47     workerThread.indexAll();
48 }
49 
50 void LogData::PartialIndexOperation::doStart(
51         LogDataWorkerThread& workerThread ) const
52 {
53     LOG(logDEBUG) << "Reindexing (partial)";
54     workerThread.indexAdditionalLines( filesize_ );
55 }
56 
57 
58 // Constructs an empty log file.
59 // It must be displayed without error.
60 LogData::LogData() : AbstractLogData(), fileWatcher_(), linePosition_(),
61     fileMutex_(), dataMutex_(), workerThread_()
62 {
63     // Start with an "empty" log
64     file_         = nullptr;
65     fileSize_     = 0;
66     nbLines_      = 0;
67     maxLength_    = 0;
68     currentOperation_ = nullptr;
69     nextOperation_    = nullptr;
70 
71     // Initialise the file watcher
72     connect( &fileWatcher_, SIGNAL( fileChanged( const QString& ) ),
73             this, SLOT( fileChangedOnDisk() ) );
74     // Forward the update signal
75     connect( &workerThread_, SIGNAL( indexingProgressed( int ) ),
76             this, SIGNAL( loadingProgressed( int ) ) );
77     connect( &workerThread_, SIGNAL( indexingFinished( bool ) ),
78             this, SLOT( indexingFinished( bool ) ) );
79 
80     // Starts the worker thread
81     workerThread_.start();
82 }
83 
84 LogData::~LogData()
85 {
86     // FIXME
87     // workerThread_.stop();
88 }
89 
90 //
91 // Public functions
92 //
93 
94 void LogData::attachFile( const QString& fileName )
95 {
96     LOG(logDEBUG) << "LogData::attachFile " << fileName.toStdString();
97 
98     if ( file_ ) {
99         // Remove the current file from the watch list
100         fileWatcher_.removeFile( file_->fileName() );
101     }
102 
103     workerThread_.interrupt();
104 
105     // If an attach operation is already in progress, the new one will
106     // be delayed until the current one is finished (canceled)
107     std::shared_ptr<const LogDataOperation> operation( new AttachOperation( fileName ) );
108     enqueueOperation( std::move( operation ) );
109 }
110 
111 void LogData::interruptLoading()
112 {
113     workerThread_.interrupt();
114 }
115 
116 qint64 LogData::getFileSize() const
117 {
118     return fileSize_;
119 }
120 
121 QDateTime LogData::getLastModifiedDate() const
122 {
123     return lastModifiedDate_;
124 }
125 
126 // Return an initialised LogFilteredData. The search is not started.
127 LogFilteredData* LogData::getNewFilteredData() const
128 {
129     LogFilteredData* newFilteredData = new LogFilteredData( this );
130 
131     return newFilteredData;
132 }
133 
134 void LogData::reload()
135 {
136     workerThread_.interrupt();
137 
138     enqueueOperation( std::make_shared<FullIndexOperation>() );
139 }
140 
141 //
142 // Private functions
143 //
144 
145 // Add an operation to the queue and perform it immediately if
146 // there is none ongoing.
147 void LogData::enqueueOperation( std::shared_ptr<const LogDataOperation> new_operation )
148 {
149     if ( currentOperation_ == nullptr )
150     {
151         // We do it immediately
152         currentOperation_ =  new_operation;
153         startOperation();
154     }
155     else
156     {
157         // An operation is in progress...
158         // ... we schedule the attach op for later
159         nextOperation_ = new_operation;
160     }
161 }
162 
163 // Performs the current operation asynchronously, a indexingFinished
164 // signal will be received when it's finished.
165 void LogData::startOperation()
166 {
167     if ( currentOperation_ )
168     {
169         LOG(logDEBUG) << "startOperation found something to do.";
170 
171         // If it's a full indexing ...
172         // ... we invalidate the non indexed data
173         if ( currentOperation_->isFull() ) {
174             fileSize_     = 0;
175             nbLines_      = 0;
176             maxLength_    = 0;
177         }
178 
179         // And let the operation do its stuff
180         currentOperation_->start( workerThread_ );
181     }
182 }
183 
184 //
185 // Slots
186 //
187 
188 void LogData::fileChangedOnDisk()
189 {
190     LOG(logDEBUG) << "signalFileChanged";
191 
192     fileWatcher_.removeFile( file_->fileName() );
193 
194     const QString name = file_->fileName();
195     QFileInfo info( name );
196 
197     std::shared_ptr<LogDataOperation> newOperation;
198 
199     LOG(logDEBUG) << "current fileSize=" << fileSize_;
200     LOG(logDEBUG) << "info file_->size()=" << info.size();
201     if ( info.size() < fileSize_ ) {
202         fileChangedOnDisk_ = Truncated;
203         LOG(logINFO) << "File truncated";
204         newOperation = std::make_shared<FullIndexOperation>();
205     }
206     else if ( fileChangedOnDisk_ != DataAdded ) {
207         fileChangedOnDisk_ = DataAdded;
208         LOG(logINFO) << "New data on disk";
209         newOperation = std::make_shared<PartialIndexOperation>( fileSize_ );
210     }
211 
212     if ( newOperation )
213         enqueueOperation( newOperation );
214 
215     lastModifiedDate_ = info.lastModified();
216 
217     emit fileChanged( fileChangedOnDisk_ );
218     // TODO: fileChangedOnDisk_, fileSize_
219 }
220 
221 void LogData::indexingFinished( bool success )
222 {
223     LOG(logDEBUG) << "Entering LogData::indexingFinished.";
224 
225     // We use the newly created file data or restore the old ones.
226     // (Qt implicit copy makes this fast!)
227     {
228         QMutexLocker locker( &dataMutex_ );
229         workerThread_.getIndexingData( &fileSize_, &maxLength_, &linePosition_ );
230         nbLines_ = linePosition_.size();
231     }
232 
233     LOG(logDEBUG) << "indexingFinished: " << success <<
234         ", found " << nbLines_ << " lines.";
235 
236     if ( success ) {
237         // Use the new filename if needed
238         if ( !currentOperation_->getFilename().isNull() ) {
239             QString newFileName = currentOperation_->getFilename();
240 
241             if ( file_ ) {
242                 QMutexLocker locker( &fileMutex_ );
243                 file_->setFileName( newFileName );
244             }
245             else {
246                 QMutexLocker locker( &fileMutex_ );
247                 file_.reset( new QFile( newFileName ) );
248             }
249         }
250 
251         // Update the modified date/time if the file exists
252         lastModifiedDate_ = QDateTime();
253         QFileInfo fileInfo( *file_ );
254         if ( fileInfo.exists() )
255             lastModifiedDate_ = fileInfo.lastModified();
256     }
257 
258     if ( file_ ) {
259         // And we watch the file for updates
260         fileChangedOnDisk_ = Unchanged;
261         fileWatcher_.addFile( file_->fileName() );
262     }
263 
264     emit loadingFinished( success );
265 
266     // So now the operation is done, let's see if there is something
267     // else to do, in which case, do it!
268     assert( currentOperation_ );
269 
270     currentOperation_ = std::move( nextOperation_ );
271     nextOperation_.reset();
272 
273     if ( currentOperation_ ) {
274         LOG(logDEBUG) << "indexingFinished is performing the next operation";
275         startOperation();
276     }
277 }
278 
279 //
280 // Implementation of virtual functions
281 //
282 qint64 LogData::doGetNbLine() const
283 {
284     return nbLines_;
285 }
286 
287 int LogData::doGetMaxLength() const
288 {
289     return maxLength_;
290 }
291 
292 int LogData::doGetLineLength( qint64 line ) const
293 {
294     if ( line >= nbLines_ ) { return 0; /* exception? */ }
295 
296     int length = doGetExpandedLineString( line ).length();
297 
298     return length;
299 }
300 
301 QString LogData::doGetLineString( qint64 line ) const
302 {
303     if ( line >= nbLines_ ) { return QString(); /* exception? */ }
304 
305     dataMutex_.lock();
306     fileMutex_.lock();
307     file_->open( QIODevice::ReadOnly );
308 
309     file_->seek( (line == 0) ? 0 : linePosition_[line-1] );
310 
311     QString string = QString( file_->readLine() );
312 
313     file_->close();
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     file_->open( QIODevice::ReadOnly );
329 
330     file_->seek( (line == 0) ? 0 : linePosition_[line-1] );
331 
332     QByteArray rawString = file_->readLine();
333 
334     file_->close();
335     fileMutex_.unlock();
336     dataMutex_.unlock();
337 
338     QString string = QString( untabify( rawString.constData() ) );
339     string.chop( 1 );
340 
341     return string;
342 }
343 
344 // Note this function is also called from the LogFilteredDataWorker thread, so
345 // data must be protected because they are changed in the main thread (by
346 // indexingFinished).
347 QStringList LogData::doGetLines( qint64 first_line, int number ) const
348 {
349     QStringList list;
350     const qint64 last_line = first_line + number - 1;
351 
352     // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number;
353 
354     if ( number == 0 ) {
355         return QStringList();
356     }
357 
358     if ( last_line >= nbLines_ ) {
359         LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for";
360         return QStringList(); /* exception? */
361     }
362 
363     dataMutex_.lock();
364 
365     fileMutex_.lock();
366     file_->open( QIODevice::ReadOnly );
367 
368     const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1];
369     const qint64 last_byte  = linePosition_[last_line];
370     // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " last_byte:" << last_byte;
371     file_->seek( first_byte );
372     QByteArray blob = file_->read( last_byte - first_byte );
373 
374     file_->close();
375     fileMutex_.unlock();
376 
377     qint64 beginning = 0;
378     qint64 end = 0;
379     for ( qint64 line = first_line; (line <= last_line); line++ ) {
380         end = linePosition_[line] - first_byte;
381         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
382         QByteArray this_line = blob.mid( beginning, end - beginning - 1 );
383         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
384         list.append( QString( this_line ) );
385         beginning = end;
386     }
387 
388     dataMutex_.unlock();
389 
390     return list;
391 }
392 
393 QStringList LogData::doGetExpandedLines( qint64 first_line, int number ) const
394 {
395     QStringList list;
396     const qint64 last_line = first_line + number - 1;
397 
398     if ( number == 0 ) {
399         return QStringList();
400     }
401 
402     if ( last_line >= nbLines_ ) {
403         LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for";
404         return QStringList(); /* exception? */
405     }
406 
407     dataMutex_.lock();
408 
409     fileMutex_.lock();
410     file_->open( QIODevice::ReadOnly );
411 
412     const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1];
413     const qint64 last_byte  = linePosition_[last_line];
414     // LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " last_byte:" << last_byte;
415     file_->seek( first_byte );
416     QByteArray blob = file_->read( last_byte - first_byte );
417 
418     file_->close();
419     fileMutex_.unlock();
420 
421     qint64 beginning = 0;
422     qint64 end = 0;
423     for ( qint64 line = first_line; (line <= last_line); line++ ) {
424         end = linePosition_[line] - first_byte;
425         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
426         QByteArray this_line = blob.mid( beginning, end - beginning - 1 );
427         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
428         list.append( untabify( this_line.constData() ) );
429         beginning = end;
430     }
431 
432     dataMutex_.unlock();
433 
434     return list;
435 }
436