xref: /glogg/src/data/logdata.cpp (revision 812146a8302488475ae27a1d22ae44a1379476a9)
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 
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( LoadingStatus ) ),
78             this, SLOT( indexingFinished( LoadingStatus ) ) );
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( LoadingStatus status )
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: " <<
234         ( status == LoadingStatus::Successful ) <<
235         ", found " << nbLines_ << " lines.";
236 
237     if ( status == LoadingStatus::Successful ) {
238         // Use the new filename if needed
239         if ( !currentOperation_->getFilename().isNull() ) {
240             QString newFileName = currentOperation_->getFilename();
241 
242             if ( file_ ) {
243                 QMutexLocker locker( &fileMutex_ );
244                 file_->setFileName( newFileName );
245             }
246             else {
247                 QMutexLocker locker( &fileMutex_ );
248                 file_.reset( new QFile( newFileName ) );
249             }
250         }
251 
252         // Update the modified date/time if the file exists
253         lastModifiedDate_ = QDateTime();
254         QFileInfo fileInfo( *file_ );
255         if ( fileInfo.exists() )
256             lastModifiedDate_ = fileInfo.lastModified();
257     }
258 
259     if ( file_ ) {
260         // And we watch the file for updates
261         fileChangedOnDisk_ = Unchanged;
262         fileWatcher_.addFile( file_->fileName() );
263     }
264 
265     emit loadingFinished( status );
266 
267     // So now the operation is done, let's see if there is something
268     // else to do, in which case, do it!
269     assert( currentOperation_ );
270 
271     currentOperation_ = std::move( nextOperation_ );
272     nextOperation_.reset();
273 
274     if ( currentOperation_ ) {
275         LOG(logDEBUG) << "indexingFinished is performing the next operation";
276         startOperation();
277     }
278 }
279 
280 //
281 // Implementation of virtual functions
282 //
283 qint64 LogData::doGetNbLine() const
284 {
285     return nbLines_;
286 }
287 
288 int LogData::doGetMaxLength() const
289 {
290     return maxLength_;
291 }
292 
293 int LogData::doGetLineLength( qint64 line ) const
294 {
295     if ( line >= nbLines_ ) { return 0; /* exception? */ }
296 
297     int length = doGetExpandedLineString( line ).length();
298 
299     return length;
300 }
301 
302 QString LogData::doGetLineString( qint64 line ) const
303 {
304     if ( line >= nbLines_ ) { return QString(); /* exception? */ }
305 
306     dataMutex_.lock();
307     fileMutex_.lock();
308     file_->open( QIODevice::ReadOnly );
309 
310     file_->seek( (line == 0) ? 0 : linePosition_[line-1] );
311 
312     QString string = QString( file_->readLine() );
313 
314     file_->close();
315     fileMutex_.unlock();
316     dataMutex_.unlock();
317 
318     string.chop( 1 );
319 
320     return string;
321 }
322 
323 QString LogData::doGetExpandedLineString( qint64 line ) const
324 {
325     if ( line >= nbLines_ ) { return QString(); /* exception? */ }
326 
327     dataMutex_.lock();
328     fileMutex_.lock();
329     file_->open( QIODevice::ReadOnly );
330 
331     file_->seek( (line == 0) ? 0 : linePosition_[line-1] );
332 
333     QByteArray rawString = file_->readLine();
334 
335     file_->close();
336     fileMutex_.unlock();
337     dataMutex_.unlock();
338 
339     QString string = QString( untabify( rawString.constData() ) );
340     string.chop( 1 );
341 
342     return string;
343 }
344 
345 // Note this function is also called from the LogFilteredDataWorker thread, so
346 // data must be protected because they are changed in the main thread (by
347 // indexingFinished).
348 QStringList LogData::doGetLines( qint64 first_line, int number ) const
349 {
350     QStringList list;
351     const qint64 last_line = first_line + number - 1;
352 
353     // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number;
354 
355     if ( number == 0 ) {
356         return QStringList();
357     }
358 
359     if ( last_line >= nbLines_ ) {
360         LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for";
361         return QStringList(); /* exception? */
362     }
363 
364     dataMutex_.lock();
365 
366     fileMutex_.lock();
367     file_->open( QIODevice::ReadOnly );
368 
369     const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1];
370     const qint64 last_byte  = linePosition_[last_line];
371     // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " last_byte:" << last_byte;
372     file_->seek( first_byte );
373     QByteArray blob = file_->read( last_byte - first_byte );
374 
375     file_->close();
376     fileMutex_.unlock();
377 
378     qint64 beginning = 0;
379     qint64 end = 0;
380     for ( qint64 line = first_line; (line <= last_line); line++ ) {
381         end = linePosition_[line] - first_byte;
382         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
383         QByteArray this_line = blob.mid( beginning, end - beginning - 1 );
384         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
385         list.append( QString( this_line ) );
386         beginning = end;
387     }
388 
389     dataMutex_.unlock();
390 
391     return list;
392 }
393 
394 QStringList LogData::doGetExpandedLines( qint64 first_line, int number ) const
395 {
396     QStringList list;
397     const qint64 last_line = first_line + number - 1;
398 
399     if ( number == 0 ) {
400         return QStringList();
401     }
402 
403     if ( last_line >= nbLines_ ) {
404         LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for";
405         return QStringList(); /* exception? */
406     }
407 
408     dataMutex_.lock();
409 
410     fileMutex_.lock();
411     file_->open( QIODevice::ReadOnly );
412 
413     const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1];
414     const qint64 last_byte  = linePosition_[last_line];
415     // LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " last_byte:" << last_byte;
416     file_->seek( first_byte );
417     QByteArray blob = file_->read( last_byte - first_byte );
418 
419     file_->close();
420     fileMutex_.unlock();
421 
422     qint64 beginning = 0;
423     qint64 end = 0;
424     for ( qint64 line = first_line; (line <= last_line); line++ ) {
425         end = linePosition_[line] - first_byte;
426         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
427         QByteArray this_line = blob.mid( beginning, end - beginning - 1 );
428         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
429         list.append( untabify( this_line.constData() ) );
430         beginning = end;
431     }
432 
433     dataMutex_.unlock();
434 
435     return list;
436 }
437