xref: /glogg/src/data/logdata.cpp (revision 821cac888d515a4e41b5d4ba4130c56db4463501)
1 /*
2  * Copyright (C) 2009, 2010 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 <QFileInfo>
25 
26 #include "log.h"
27 
28 #include "logdata.h"
29 #include "logfiltereddata.h"
30 
31 // Implementation of the 'start' functions for each operation
32 
33 void LogData::AttachOperation::doStart(
34         LogDataWorkerThread& workerThread ) const
35 {
36     LOG(logDEBUG) << "Attaching " << filename_.toStdString();
37     workerThread.attachFile( filename_ );
38     workerThread.indexAll();
39 }
40 
41 void LogData::FullIndexOperation::doStart(
42         LogDataWorkerThread& workerThread ) const
43 {
44     LOG(logDEBUG) << "Reindexing (full)";
45     workerThread.indexAll();
46 }
47 
48 void LogData::PartialIndexOperation::doStart(
49         LogDataWorkerThread& workerThread ) const
50 {
51     LOG(logDEBUG) << "Reindexing (partial)";
52     workerThread.indexAdditionalLines( filesize_ );
53 }
54 
55 
56 // Constructs an empty log file.
57 // It must be displayed without error.
58 LogData::LogData() : AbstractLogData(), fileWatcher_(), linePosition_(),
59     fileMutex_(), dataMutex_(), workerThread_()
60 {
61     // Start with an "empty" log
62     file_         = NULL;
63     fileSize_     = 0;
64     nbLines_      = 0;
65     maxLength_    = 0;
66     currentOperation_ = NULL;
67     nextOperation_    = NULL;
68 
69     // Initialise the file watcher
70     connect( &fileWatcher_, SIGNAL( fileChanged( const QString& ) ),
71             this, SLOT( fileChangedOnDisk() ) );
72     // Forward the update signal
73     connect( &workerThread_, SIGNAL( indexingProgressed( int ) ),
74             this, SIGNAL( loadingProgressed( int ) ) );
75     connect( &workerThread_, SIGNAL( indexingFinished( bool ) ),
76             this, SLOT( indexingFinished( bool ) ) );
77 
78     // Starts the worker thread
79     workerThread_.start();
80 }
81 
82 LogData::~LogData()
83 {
84     if ( file_ )
85         delete file_;
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     LogDataOperation* newOperation = new AttachOperation( fileName );
104 
105     // If an attach operation is already in progress, the new one will
106     // be delayed untilthe current one is finished (canceled)
107     enqueueOperation( newOperation );
108 }
109 
110 void LogData::interruptLoading()
111 {
112     workerThread_.interrupt();
113 }
114 
115 qint64 LogData::getFileSize() const
116 {
117     return fileSize_;
118 }
119 
120 QDateTime LogData::getLastModifiedDate() const
121 {
122     return lastModifiedDate_;
123 }
124 
125 // Return an initialised LogFilteredData. The search is not started.
126 LogFilteredData* LogData::getNewFilteredData() const
127 {
128     LogFilteredData* newFilteredData = new LogFilteredData( this );
129 
130     return newFilteredData;
131 }
132 
133 // Add an operation to the queue and perform it immediately if
134 // there is none ongoing.
135 void LogData::enqueueOperation( const LogDataOperation* newOperation )
136 {
137     if ( currentOperation_ == NULL )
138     {
139         // We do it immediately
140         currentOperation_ = newOperation;
141         startOperation();
142     }
143     else
144     {
145         // An operation is in progress...
146         // ... we schedule the attach op for later
147         if ( nextOperation_ != NULL )
148             delete nextOperation_;
149         nextOperation_ = newOperation;
150     }
151 }
152 
153 // Performs the current operation asynchronously, a indexingFinished
154 // signal will be received when it's finished.
155 void LogData::startOperation()
156 {
157     if ( currentOperation_ )
158     {
159         LOG(logDEBUG) << "startOperation found something to do.";
160 
161         // If it's a full indexing ...
162         // ... we invalidate the non indexed data
163         if ( currentOperation_->isFull() ) {
164             fileSize_     = 0;
165             nbLines_      = 0;
166             maxLength_    = 0;
167         }
168 
169         // And let the operation do its stuff
170         currentOperation_->start( workerThread_ );
171     }
172 }
173 
174 //
175 // Slots
176 //
177 
178 void LogData::fileChangedOnDisk()
179 {
180     LOG(logDEBUG) << "signalFileChanged";
181 
182     fileWatcher_.removeFile( file_->fileName() );
183 
184     const QString name = file_->fileName();
185     QFileInfo info( name );
186 
187     LogDataOperation* newOperation = NULL;
188 
189     LOG(logDEBUG) << "current fileSize=" << fileSize_;
190     LOG(logDEBUG) << "info file_->size()=" << info.size();
191     if ( info.size() < fileSize_ ) {
192         fileChangedOnDisk_ = Truncated;
193         LOG(logINFO) << "File truncated";
194         newOperation = new FullIndexOperation();
195     }
196     else if ( fileChangedOnDisk_ != DataAdded ) {
197         fileChangedOnDisk_ = DataAdded;
198         LOG(logINFO) << "New data on disk";
199         newOperation = new PartialIndexOperation( fileSize_ );
200     }
201 
202     if ( newOperation )
203         enqueueOperation( newOperation );
204 
205     lastModifiedDate_ = info.lastModified();
206 
207     emit fileChanged( fileChangedOnDisk_ );
208     // TODO: fileChangedOnDisk_, fileSize_
209 }
210 
211 void LogData::indexingFinished( bool success )
212 {
213     LOG(logDEBUG) << "Entering LogData::indexingFinished.";
214 
215     // We use the newly created file data or restore the old ones.
216     // (Qt implicit copy makes this fast!)
217     {
218         QMutexLocker locker( &dataMutex_ );
219         workerThread_.getIndexingData( &fileSize_, &maxLength_, &linePosition_ );
220         nbLines_ = linePosition_.size();
221     }
222 
223     LOG(logDEBUG) << "indexingFinished: " << success <<
224         ", found " << nbLines_ << " lines.";
225 
226     if ( success ) {
227         // Use the new filename if needed
228         if ( !currentOperation_->getFilename().isNull() ) {
229             QString newFileName = currentOperation_->getFilename();
230 
231             if ( file_ ) {
232                 QMutexLocker locker( &fileMutex_ );
233                 file_->setFileName( newFileName );
234             }
235             else {
236                 QMutexLocker locker( &fileMutex_ );
237                 file_ = new QFile( newFileName );
238             }
239         }
240 
241         // Update the modified date/time if the file exists
242         lastModifiedDate_ = QDateTime();
243         QFileInfo fileInfo( *file_ );
244         if ( fileInfo.exists() )
245             lastModifiedDate_ = fileInfo.lastModified();
246     }
247 
248     if ( file_ ) {
249         // And we watch the file for updates
250         fileChangedOnDisk_ = Unchanged;
251         fileWatcher_.addFile( file_->fileName() );
252     }
253 
254     emit loadingFinished( success );
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     if ( currentOperation_ ) {
259         delete currentOperation_;
260         currentOperation_ = nextOperation_;
261         nextOperation_ = NULL;
262     }
263     else {
264         LOG(logERROR) << "currentOperation_ is NULL in indexingFinished()";
265     }
266 
267     if ( currentOperation_ ) {
268         LOG(logDEBUG) << "indexingFinished is performing the next operation";
269         startOperation();
270     }
271 }
272 
273 //
274 // Implementation of virtual functions
275 //
276 qint64 LogData::doGetNbLine() const
277 {
278     return nbLines_;
279 }
280 
281 int LogData::doGetMaxLength() const
282 {
283     return maxLength_;
284 }
285 
286 int LogData::doGetLineLength( qint64 line ) const
287 {
288     if ( line >= nbLines_ ) { return 0; /* exception? */ }
289 
290     int length = doGetExpandedLineString( line ).length();
291 
292     return length;
293 }
294 
295 QString LogData::doGetLineString( qint64 line ) const
296 {
297     if ( line >= nbLines_ ) { return QString(); /* exception? */ }
298 
299     dataMutex_.lock();
300     fileMutex_.lock();
301     file_->open( QIODevice::ReadOnly );
302 
303     file_->seek( (line == 0) ? 0 : linePosition_[line-1] );
304 
305     QString string = QString( file_->readLine() );
306 
307     file_->close();
308     fileMutex_.unlock();
309     dataMutex_.unlock();
310 
311     string.chop( 1 );
312 
313     return string;
314 }
315 
316 QString LogData::doGetExpandedLineString( qint64 line ) const
317 {
318     if ( line >= nbLines_ ) { return QString(); /* exception? */ }
319 
320     dataMutex_.lock();
321     fileMutex_.lock();
322     file_->open( QIODevice::ReadOnly );
323 
324     file_->seek( (line == 0) ? 0 : linePosition_[line-1] );
325 
326     QByteArray rawString = file_->readLine();
327 
328     file_->close();
329     fileMutex_.unlock();
330     dataMutex_.unlock();
331 
332     QString string = QString( untabify( rawString.constData() ) );
333     string.chop( 1 );
334 
335     return string;
336 }
337 
338 // Note this function is also called from the LogFilteredDataWorker thread, so
339 // data must be protected because they are changed in the main thread (by
340 // indexingFinished).
341 QStringList LogData::doGetLines( qint64 first_line, int number ) const
342 {
343     QStringList list;
344     const qint64 last_line = first_line + number - 1;
345 
346     // LOG(logDEBUG) << "LogData::doGetLines first_line:" << first_line << " nb:" << number;
347 
348     if ( number == 0 ) {
349         return QStringList();
350     }
351 
352     if ( last_line >= nbLines_ ) {
353         LOG(logWARNING) << "LogData::doGetLines Lines out of bound asked for";
354         return QStringList(); /* exception? */
355     }
356 
357     dataMutex_.lock();
358 
359     fileMutex_.lock();
360     file_->open( QIODevice::ReadOnly );
361 
362     const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1];
363     const qint64 last_byte  = linePosition_[last_line];
364     // LOG(logDEBUG) << "LogData::doGetLines first_byte:" << first_byte << " last_byte:" << last_byte;
365     file_->seek( first_byte );
366     QByteArray blob = file_->read( last_byte - first_byte );
367 
368     file_->close();
369     fileMutex_.unlock();
370 
371     qint64 beginning = 0;
372     qint64 end = 0;
373     for ( qint64 line = first_line; (line <= last_line); line++ ) {
374         end = linePosition_[line] - first_byte;
375         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
376         QByteArray this_line = blob.mid( beginning, end - beginning - 1 );
377         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
378         list.append( QString( this_line ) );
379         beginning = end;
380     }
381 
382     dataMutex_.unlock();
383 
384     return list;
385 }
386 
387 QStringList LogData::doGetExpandedLines( qint64 first_line, int number ) const
388 {
389     QStringList list;
390     const qint64 last_line = first_line + number - 1;
391 
392     if ( number == 0 ) {
393         return QStringList();
394     }
395 
396     if ( last_line >= nbLines_ ) {
397         LOG(logWARNING) << "LogData::doGetExpandedLines Lines out of bound asked for";
398         return QStringList(); /* exception? */
399     }
400 
401     dataMutex_.lock();
402 
403     fileMutex_.lock();
404     file_->open( QIODevice::ReadOnly );
405 
406     const qint64 first_byte = (first_line == 0) ? 0 : linePosition_[first_line-1];
407     const qint64 last_byte  = linePosition_[last_line];
408     // LOG(logDEBUG) << "LogData::doGetExpandedLines first_byte:" << first_byte << " last_byte:" << last_byte;
409     file_->seek( first_byte );
410     QByteArray blob = file_->read( last_byte - first_byte );
411 
412     file_->close();
413     fileMutex_.unlock();
414 
415     qint64 beginning = 0;
416     qint64 end = 0;
417     for ( qint64 line = first_line; (line <= last_line); line++ ) {
418         end = linePosition_[line] - first_byte;
419         // LOG(logDEBUG) << "Getting line " << line << " beginning " << beginning << " end " << end;
420         QByteArray this_line = blob.mid( beginning, end - beginning - 1 );
421         // LOG(logDEBUG) << "Line is: " << QString( this_line ).toStdString();
422         list.append( untabify( this_line.constData() ) );
423         beginning = end;
424     }
425 
426     dataMutex_.unlock();
427 
428     return list;
429 }
430