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