xref: /glogg/src/data/logdata.cpp (revision 2686d8e51c39a61a0e1014dc41717bacb2c2918a)
1 /*
2  * Copyright (C) 2009, 2010, 2013, 2014, 2015 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(), indexing_data_(),
66     fileMutex_(), workerThread_( &indexing_data_ )
67 {
68     // Start with an "empty" log
69     attached_file_ = nullptr;
70     currentOperation_ = nullptr;
71     nextOperation_    = nullptr;
72 
73 #if defined(GLOGG_SUPPORTS_INOTIFY) || defined(WIN32)
74     fileWatcher_ = std::make_shared<PlatformFileWatcher>();
75 #else
76     fileWatcher_ = std::make_shared<QtFileWatcher>();
77 #endif
78 
79     // Initialise the file watcher
80     connect( fileWatcher_.get(), SIGNAL( fileChanged( const QString& ) ),
81             this, SLOT( fileChangedOnDisk() ) );
82     // Forward the update signal
83     connect( &workerThread_, SIGNAL( indexingProgressed( int ) ),
84             this, SIGNAL( loadingProgressed( int ) ) );
85     connect( &workerThread_, SIGNAL( indexingFinished( LoadingStatus ) ),
86             this, SLOT( indexingFinished( LoadingStatus ) ) );
87 
88     // Starts the worker thread
89     workerThread_.start();
90 }
91 
92 LogData::~LogData()
93 {
94     // Remove the current file from the watch list
95     if ( attached_file_ )
96         fileWatcher_->removeFile( attached_file_->fileName() );
97 
98     // FIXME
99     // workerThread_.stop();
100 }
101 
102 //
103 // Public functions
104 //
105 
106 void LogData::attachFile( const QString& fileName )
107 {
108     LOG(logDEBUG) << "LogData::attachFile " << fileName.toStdString();
109 
110     if ( attached_file_ ) {
111         // We cannot reattach
112         throw CantReattachErr();
113     }
114 
115     attached_file_.reset( new QFile( fileName ) );
116     attached_file_->open( QIODevice::ReadOnly );
117 
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 indexing_data_.getSize();
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 void LogData::setPollingInterval( uint32_t interval_ms )
153 {
154     fileWatcher_->setPollingInterval( interval_ms );
155 }
156 
157 //
158 // Private functions
159 //
160 
161 // Add an operation to the queue and perform it immediately if
162 // there is none ongoing.
163 void LogData::enqueueOperation( std::shared_ptr<const LogDataOperation> new_operation )
164 {
165     if ( currentOperation_ == nullptr )
166     {
167         // We do it immediately
168         currentOperation_ =  new_operation;
169         startOperation();
170     }
171     else
172     {
173         // An operation is in progress...
174         // ... we schedule the attach op for later
175         nextOperation_ = new_operation;
176     }
177 }
178 
179 // Performs the current operation asynchronously, a indexingFinished
180 // signal will be received when it's finished.
181 void LogData::startOperation()
182 {
183     if ( currentOperation_ )
184     {
185         LOG(logDEBUG) << "startOperation found something to do.";
186 
187         // Let the operation do its stuff
188         currentOperation_->start( workerThread_ );
189     }
190 }
191 
192 //
193 // Slots
194 //
195 
196 void LogData::fileChangedOnDisk()
197 {
198     LOG(logDEBUG) << "signalFileChanged";
199 
200     const QString name = attached_file_->fileName();
201     QFileInfo info( name );
202 
203     // Need to open the file in case it was absent
204     attached_file_->open( QIODevice::ReadOnly );
205 
206     std::shared_ptr<LogDataOperation> newOperation;
207 
208     qint64 file_size = indexing_data_.getSize();
209     LOG(logDEBUG) << "current fileSize=" << file_size;
210     LOG(logDEBUG) << "info file_->size()=" << info.size();
211     if ( info.size() < file_size ) {
212         fileChangedOnDisk_ = Truncated;
213         LOG(logINFO) << "File truncated";
214         newOperation = std::make_shared<FullIndexOperation>();
215     }
216     else if ( fileChangedOnDisk_ != DataAdded ) {
217         fileChangedOnDisk_ = DataAdded;
218         LOG(logINFO) << "New data on disk";
219         newOperation = std::make_shared<PartialIndexOperation>( file_size );
220     }
221 
222     if ( newOperation )
223         enqueueOperation( newOperation );
224 
225     lastModifiedDate_ = info.lastModified();
226 
227     emit fileChanged( fileChangedOnDisk_ );
228     // TODO: fileChangedOnDisk_, fileSize_
229 }
230 
231 void LogData::indexingFinished( LoadingStatus status )
232 {
233     LOG(logDEBUG) << "indexingFinished: " <<
234         ( status == LoadingStatus::Successful ) <<
235         ", found " << indexing_data_.getNbLines() << " lines.";
236 
237     if ( status == LoadingStatus::Successful ) {
238         // Start watching we watch the file for updates
239         fileChangedOnDisk_ = Unchanged;
240         fileWatcher_->addFile( attached_file_->fileName() );
241 
242         // Update the modified date/time if the file exists
243         lastModifiedDate_ = QDateTime();
244         QFileInfo fileInfo( *attached_file_ );
245         if ( fileInfo.exists() )
246             lastModifiedDate_ = fileInfo.lastModified();
247     }
248 
249     // FIXME be cleverer here as a notification might have arrived whilst we
250     // were indexing.
251     fileChangedOnDisk_ = Unchanged;
252 
253     LOG(logDEBUG) << "Sending indexingFinished.";
254     emit loadingFinished( status );
255 
256     // So now the operation is done, let's see if there is something
257     // else to do, in which case, do it!
258     assert( currentOperation_ );
259 
260     currentOperation_ = std::move( nextOperation_ );
261     nextOperation_.reset();
262 
263     if ( currentOperation_ ) {
264         LOG(logDEBUG) << "indexingFinished is performing the next operation";
265         startOperation();
266     }
267 }
268 
269 //
270 // Implementation of virtual functions
271 //
272 qint64 LogData::doGetNbLine() const
273 {
274     return indexing_data_.getNbLines();
275 }
276 
277 int LogData::doGetMaxLength() const
278 {
279     return indexing_data_.getMaxLength();
280 }
281 
282 int LogData::doGetLineLength( qint64 line ) const
283 {
284     if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ }
285 
286     int length = doGetExpandedLineString( line ).length();
287 
288     return length;
289 }
290 
291 QString LogData::doGetLineString( qint64 line ) const
292 {
293     if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ }
294 
295     fileMutex_.lock();
296 
297     attached_file_->seek( (line == 0) ? 0 : indexing_data_.getPosForLine( line-1 ) );
298 
299     QString string = QString( attached_file_->readLine() );
300 
301     fileMutex_.unlock();
302 
303     string.chop( 1 );
304 
305     return string;
306 }
307 
308 QString LogData::doGetExpandedLineString( qint64 line ) const
309 {
310     if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ }
311 
312     fileMutex_.lock();
313 
314     attached_file_->seek( (line == 0) ? 0 : indexing_data_.getPosForLine( line-1 ) );
315 
316     QByteArray rawString = attached_file_->readLine();
317 
318     fileMutex_.unlock();
319 
320     QString string = QString( untabify( rawString.constData() ) );
321     string.chop( 1 );
322 
323     return string;
324 }
325 
326 // Note this function is also called from the LogFilteredDataWorker thread, so
327 // data must be protected because they are changed in the main thread (by
328 // indexingFinished).
329 QStringList LogData::doGetLines( qint64 first_line, int number ) const
330 {
331     QStringList list;
332     const qint64 last_line = first_line + number - 1;
333 
334     // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number;
335 
336     if ( number == 0 ) {
337         return QStringList();
338     }
339 
340     if ( last_line >= indexing_data_.getNbLines() ) {
341         LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for";
342         return QStringList(); /* exception? */
343     }
344 
345     fileMutex_.lock();
346 
347     const qint64 first_byte = (first_line == 0) ?
348         0 : indexing_data_.getPosForLine( first_line-1 );
349     const qint64 last_byte  = indexing_data_.getPosForLine( last_line );
350     // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " last_byte:" << last_byte;
351     attached_file_->seek( first_byte );
352     QByteArray blob = attached_file_->read( last_byte - first_byte );
353 
354     fileMutex_.unlock();
355 
356     qint64 beginning = 0;
357     qint64 end = 0;
358     for ( qint64 line = first_line; (line <= last_line); line++ ) {
359         end = indexing_data_.getPosForLine( line ) - first_byte;
360         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
361         QByteArray this_line = blob.mid( beginning, end - beginning - 1 );
362         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
363         list.append( QString( this_line ) );
364         beginning = end;
365     }
366 
367     return list;
368 }
369 
370 QStringList LogData::doGetExpandedLines( qint64 first_line, int number ) const
371 {
372     QStringList list;
373     const qint64 last_line = first_line + number - 1;
374 
375     if ( number == 0 ) {
376         return QStringList();
377     }
378 
379     if ( last_line >= indexing_data_.getNbLines() ) {
380         LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for";
381         return QStringList(); /* exception? */
382     }
383 
384     fileMutex_.lock();
385 
386     const qint64 first_byte = (first_line == 0) ?
387         0 : indexing_data_.getPosForLine( first_line-1 );
388     const qint64 last_byte  = indexing_data_.getPosForLine( last_line );
389     // LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " last_byte:" << last_byte;
390 
391     attached_file_->seek( first_byte );
392     QByteArray blob = attached_file_->read( last_byte - first_byte );
393 
394     fileMutex_.unlock();
395 
396     qint64 beginning = 0;
397     qint64 end = 0;
398     for ( qint64 line = first_line; (line <= last_line); line++ ) {
399         end = indexing_data_.getPosForLine( 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( untabify( this_line.constData() ) );
404         beginning = end;
405     }
406 
407     return list;
408 }
409