xref: /glogg/src/data/logdata.cpp (revision 4fb0346e73d7caa82d42531c8c8681b5eb607728)
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();
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     codec_ = QTextCodec::codecForName( "ISO-8859-1" );
74 
75 #if defined(GLOGG_SUPPORTS_INOTIFY) || defined(WIN32)
76     fileWatcher_ = std::make_shared<PlatformFileWatcher>();
77 #else
78     fileWatcher_ = std::make_shared<QtFileWatcher>();
79 #endif
80 
81     // Initialise the file watcher
82     connect( fileWatcher_.get(), SIGNAL( fileChanged( const QString& ) ),
83             this, SLOT( fileChangedOnDisk() ) );
84     // Forward the update signal
85     connect( &workerThread_, SIGNAL( indexingProgressed( int ) ),
86             this, SIGNAL( loadingProgressed( int ) ) );
87     connect( &workerThread_, SIGNAL( indexingFinished( LoadingStatus ) ),
88             this, SLOT( indexingFinished( LoadingStatus ) ) );
89 
90     // Starts the worker thread
91     workerThread_.start();
92 }
93 
94 LogData::~LogData()
95 {
96     // Remove the current file from the watch list
97     if ( attached_file_ )
98         fileWatcher_->removeFile( attached_file_->fileName() );
99 
100     // FIXME
101     // workerThread_.stop();
102 }
103 
104 //
105 // Public functions
106 //
107 
108 void LogData::attachFile( const QString& fileName )
109 {
110     LOG(logDEBUG) << "LogData::attachFile " << fileName.toStdString();
111 
112     if ( attached_file_ ) {
113         // We cannot reattach
114         throw CantReattachErr();
115     }
116 
117     attached_file_.reset( new QFile( fileName ) );
118     attached_file_->open( QIODevice::ReadOnly );
119 
120     std::shared_ptr<const LogDataOperation> operation( new AttachOperation( fileName ) );
121     enqueueOperation( std::move( operation ) );
122 }
123 
124 void LogData::interruptLoading()
125 {
126     workerThread_.interrupt();
127 }
128 
129 qint64 LogData::getFileSize() const
130 {
131     return indexing_data_.getSize();
132 }
133 
134 QDateTime LogData::getLastModifiedDate() const
135 {
136     return lastModifiedDate_;
137 }
138 
139 // Return an initialised LogFilteredData. The search is not started.
140 LogFilteredData* LogData::getNewFilteredData() const
141 {
142     LogFilteredData* newFilteredData = new LogFilteredData( this );
143 
144     return newFilteredData;
145 }
146 
147 void LogData::reload()
148 {
149     workerThread_.interrupt();
150 
151     enqueueOperation( std::make_shared<FullIndexOperation>() );
152 }
153 
154 void LogData::setPollingInterval( uint32_t interval_ms )
155 {
156     fileWatcher_->setPollingInterval( interval_ms );
157 }
158 
159 //
160 // Private functions
161 //
162 
163 // Add an operation to the queue and perform it immediately if
164 // there is none ongoing.
165 void LogData::enqueueOperation( std::shared_ptr<const LogDataOperation> new_operation )
166 {
167     if ( currentOperation_ == nullptr )
168     {
169         // We do it immediately
170         currentOperation_ =  new_operation;
171         startOperation();
172     }
173     else
174     {
175         // An operation is in progress...
176         // ... we schedule the attach op for later
177         nextOperation_ = new_operation;
178     }
179 }
180 
181 // Performs the current operation asynchronously, a indexingFinished
182 // signal will be received when it's finished.
183 void LogData::startOperation()
184 {
185     if ( currentOperation_ )
186     {
187         LOG(logDEBUG) << "startOperation found something to do.";
188 
189         // Let the operation do its stuff
190         currentOperation_->start( workerThread_ );
191     }
192 }
193 
194 //
195 // Slots
196 //
197 
198 void LogData::fileChangedOnDisk()
199 {
200     LOG(logDEBUG) << "signalFileChanged";
201 
202     const QString name = attached_file_->fileName();
203     QFileInfo info( name );
204 
205     // Need to open the file in case it was absent
206     attached_file_->open( QIODevice::ReadOnly );
207 
208     std::shared_ptr<LogDataOperation> newOperation;
209 
210     qint64 file_size = indexing_data_.getSize();
211     LOG(logDEBUG) << "current fileSize=" << file_size;
212     LOG(logDEBUG) << "info file_->size()=" << info.size();
213     if ( info.size() < file_size ) {
214         fileChangedOnDisk_ = Truncated;
215         LOG(logINFO) << "File truncated";
216         newOperation = std::make_shared<FullIndexOperation>();
217     }
218     else if ( fileChangedOnDisk_ != DataAdded ) {
219         fileChangedOnDisk_ = DataAdded;
220         LOG(logINFO) << "New data on disk";
221         newOperation = std::make_shared<PartialIndexOperation>();
222     }
223 
224     if ( newOperation )
225         enqueueOperation( newOperation );
226 
227     lastModifiedDate_ = info.lastModified();
228 
229     emit fileChanged( fileChangedOnDisk_ );
230     // TODO: fileChangedOnDisk_, fileSize_
231 }
232 
233 void LogData::indexingFinished( LoadingStatus status )
234 {
235     LOG(logDEBUG) << "indexingFinished: " <<
236         ( status == LoadingStatus::Successful ) <<
237         ", found " << indexing_data_.getNbLines() << " lines.";
238 
239     if ( status == LoadingStatus::Successful ) {
240         // Start watching we watch the file for updates
241         fileChangedOnDisk_ = Unchanged;
242         fileWatcher_->addFile( attached_file_->fileName() );
243 
244         // Update the modified date/time if the file exists
245         lastModifiedDate_ = QDateTime();
246         QFileInfo fileInfo( *attached_file_ );
247         if ( fileInfo.exists() )
248             lastModifiedDate_ = fileInfo.lastModified();
249     }
250 
251     // FIXME be cleverer here as a notification might have arrived whilst we
252     // were indexing.
253     fileChangedOnDisk_ = Unchanged;
254 
255     LOG(logDEBUG) << "Sending indexingFinished.";
256     emit loadingFinished( status );
257 
258     // So now the operation is done, let's see if there is something
259     // else to do, in which case, do it!
260     assert( currentOperation_ );
261 
262     currentOperation_ = std::move( nextOperation_ );
263     nextOperation_.reset();
264 
265     if ( currentOperation_ ) {
266         LOG(logDEBUG) << "indexingFinished is performing the next operation";
267         startOperation();
268     }
269 }
270 
271 //
272 // Implementation of virtual functions
273 //
274 qint64 LogData::doGetNbLine() const
275 {
276     return indexing_data_.getNbLines();
277 }
278 
279 int LogData::doGetMaxLength() const
280 {
281     return indexing_data_.getMaxLength();
282 }
283 
284 int LogData::doGetLineLength( qint64 line ) const
285 {
286     if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ }
287 
288     int length = doGetExpandedLineString( line ).length();
289 
290     return length;
291 }
292 
293 void LogData::doSetDisplayEncoding( const char* encoding )
294 {
295     LOG(logDEBUG) << "AbstractLogData::setDisplayEncoding: " << encoding;
296     codec_ = QTextCodec::codecForName( encoding );
297 }
298 
299 void LogData::doSetMultibyteEncodingOffsets( int before_cr, int after_cr )
300 {
301     before_cr_offset_ = before_cr;
302     after_cr_offset_ = after_cr;
303 }
304 
305 QString LogData::doGetLineString( qint64 line ) const
306 {
307     if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ }
308 
309     fileMutex_.lock();
310 
311     // end_byte is non-inclusive.(is not read)
312     const qint64 first_byte = (line == 0) ?
313         0 : ( indexing_data_.getPosForLine( line-1 ) + after_cr_offset_ );
314     const qint64 end_byte  = indexing_data_.getPosForLine( line ) - 1 - before_cr_offset_;
315 
316     attached_file_->seek( first_byte );
317 
318     QString string = codec_->toUnicode( attached_file_->read( end_byte - first_byte ) );
319 
320     fileMutex_.unlock();
321 
322     return string;
323 }
324 
325 QString LogData::doGetExpandedLineString( qint64 line ) const
326 {
327     if ( line >= indexing_data_.getNbLines() ) { return 0; /* exception? */ }
328 
329     fileMutex_.lock();
330 
331     // end_byte is non-inclusive.(is not read) We also exclude the final \r.
332     const qint64 first_byte = (line == 0) ?
333         0 : ( indexing_data_.getPosForLine( line-1 ) + after_cr_offset_ );
334     const qint64 end_byte  = indexing_data_.getPosForLine( line ) - 1 - before_cr_offset_;
335 
336     attached_file_->seek( first_byte );
337 
338     // LOG(logDEBUG) << "LogData::doGetExpandedLineString first_byte:" << first_byte << " end_byte:" << end_byte;
339     QByteArray rawString = attached_file_->read( end_byte - first_byte );
340 
341     fileMutex_.unlock();
342 
343     QString string = untabify( codec_->toUnicode( rawString ) );
344 
345     // LOG(logDEBUG) << "doGetExpandedLineString Line is: " << string.toStdString();
346 
347     return string;
348 }
349 
350 // Note this function is also called from the LogFilteredDataWorker thread, so
351 // data must be protected because they are changed in the main thread (by
352 // indexingFinished).
353 QStringList LogData::doGetLines( qint64 first_line, int number ) const
354 {
355     QStringList list;
356     const qint64 last_line = first_line + number - 1;
357 
358     // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number;
359 
360     if ( number == 0 ) {
361         return QStringList();
362     }
363 
364     if ( last_line >= indexing_data_.getNbLines() ) {
365         LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for";
366         return QStringList(); /* exception? */
367     }
368 
369     fileMutex_.lock();
370 
371     const qint64 first_byte = (first_line == 0) ?
372         0 : ( indexing_data_.getPosForLine( first_line-1 ) + after_cr_offset_ );
373     const qint64 end_byte  = indexing_data_.getPosForLine( last_line ) - 1 - before_cr_offset_;
374     // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " end_byte:" << end_byte;
375     attached_file_->seek( first_byte );
376     QByteArray blob = attached_file_->read( end_byte - first_byte );
377 
378     fileMutex_.unlock();
379 
380     qint64 beginning = 0;
381     qint64 end = 0;
382     for ( qint64 line = first_line; (line <= last_line); line++ ) {
383         end = indexing_data_.getPosForLine( line ) + after_cr_offset_ - first_byte;
384         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
385         QByteArray this_line = blob.mid( beginning, end - beginning );
386         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
387         list.append( codec_->toUnicode( this_line ) );
388         beginning = end + 1 + before_cr_offset_ + after_cr_offset_;
389     }
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 >= indexing_data_.getNbLines() ) {
404         LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for";
405         return QStringList(); /* exception? */
406     }
407 
408     fileMutex_.lock();
409 
410     // end_byte is non-inclusive.(is not read)
411     const qint64 first_byte = (first_line == 0) ?
412         0 : ( indexing_data_.getPosForLine( first_line-1 ) + after_cr_offset_ );
413     const qint64 end_byte  = indexing_data_.getPosForLine( last_line ) - 1 - before_cr_offset_;
414     LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " end_byte:" << end_byte;
415 
416     attached_file_->seek( first_byte );
417     QByteArray blob = attached_file_->read( end_byte - first_byte );
418 
419     fileMutex_.unlock();
420 
421     qint64 beginning = 0;
422     qint64 end = 0;
423     for ( qint64 line = first_line; (line <= last_line); line++ ) {
424         // end is non-inclusive
425         // LOG(logDEBUG) << "EoL " << line << ": " << indexing_data_.getPosForLine( line );
426         end = indexing_data_.getPosForLine( line ) - 1 - before_cr_offset_ - first_byte;
427         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
428         QByteArray this_line = blob.mid( beginning, end - beginning );
429         QString conv_line = codec_->toUnicode( this_line );
430         // LOG(logDEBUG) << "Line is: " << conv_line.toStdString();
431         list.append( untabify( conv_line ) );
432         beginning = end + 1 + before_cr_offset_ + after_cr_offset_;
433     }
434 
435     return list;
436 }
437 
438 EncodingSpeculator::Encoding LogData::getDetectedEncoding() const
439 {
440     return indexing_data_.getEncodingGuess();
441 }
442