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