xref: /glogg/src/data/logdataworkerthread.cpp (revision c633ced33b4f2c2cf77c0c80a15fa73a7f13ad9f)
1 /*
2  * Copyright (C) 2009, 2010, 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 #include <QFile>
21 
22 #include "log.h"
23 
24 #include "logdata.h"
25 #include "logdataworkerthread.h"
26 
27 // Size of the chunk to read (5 MiB)
28 const int IndexOperation::sizeChunk = 5*1024*1024;
29 
30 qint64 IndexingData::getSize() const
31 {
32     QMutexLocker locker( &dataMutex_ );
33 
34     return indexedSize_;
35 }
36 
37 int IndexingData::getMaxLength() const
38 {
39     QMutexLocker locker( &dataMutex_ );
40 
41     return maxLength_;
42 }
43 
44 LineNumber IndexingData::getNbLines() const
45 {
46     QMutexLocker locker( &dataMutex_ );
47 
48     return linePosition_.size();
49 }
50 
51 qint64 IndexingData::getPosForLine( LineNumber line ) const
52 {
53     QMutexLocker locker( &dataMutex_ );
54 
55     return linePosition_.at( line );
56 }
57 
58 void IndexingData::addAll( qint64 size, int length,
59         const FastLinePositionArray& linePosition )
60 {
61     QMutexLocker locker( &dataMutex_ );
62 
63     indexedSize_  += size;
64     maxLength_     = qMax( maxLength_, length );
65     linePosition_.append_list( linePosition );
66 }
67 
68 void IndexingData::clear()
69 {
70     maxLength_   = 0;
71     indexedSize_ = 0;
72     linePosition_ = LinePositionArray();
73 }
74 
75 LogDataWorkerThread::LogDataWorkerThread( IndexingData* indexing_data )
76     : QThread(), mutex_(), operationRequestedCond_(),
77     nothingToDoCond_(), fileName_(), indexing_data_( indexing_data )
78 {
79     terminate_          = false;
80     interruptRequested_ = false;
81     operationRequested_ = NULL;
82 }
83 
84 LogDataWorkerThread::~LogDataWorkerThread()
85 {
86     {
87         QMutexLocker locker( &mutex_ );
88         terminate_ = true;
89         operationRequestedCond_.wakeAll();
90     }
91     wait();
92 }
93 
94 void LogDataWorkerThread::attachFile( const QString& fileName )
95 {
96     QMutexLocker locker( &mutex_ );  // to protect fileName_
97 
98     fileName_ = fileName;
99 }
100 
101 void LogDataWorkerThread::indexAll()
102 {
103     QMutexLocker locker( &mutex_ );  // to protect operationRequested_
104 
105     LOG(logDEBUG) << "FullIndex requested";
106 
107     // If an operation is ongoing, we will block
108     while ( (operationRequested_ != NULL) )
109         nothingToDoCond_.wait( &mutex_ );
110 
111     interruptRequested_ = false;
112     operationRequested_ = new FullIndexOperation( fileName_,
113             indexing_data_, &interruptRequested_ );
114     operationRequestedCond_.wakeAll();
115 }
116 
117 void LogDataWorkerThread::indexAdditionalLines( qint64 position )
118 {
119     QMutexLocker locker( &mutex_ );  // to protect operationRequested_
120 
121     LOG(logDEBUG) << "AddLines requested";
122 
123     // If an operation is ongoing, we will block
124     while ( (operationRequested_ != NULL) )
125         nothingToDoCond_.wait( &mutex_ );
126 
127     interruptRequested_ = false;
128     operationRequested_ = new PartialIndexOperation( fileName_,
129             indexing_data_, &interruptRequested_, position );
130     operationRequestedCond_.wakeAll();
131 }
132 
133 void LogDataWorkerThread::interrupt()
134 {
135     LOG(logDEBUG) << "Load interrupt requested";
136 
137     // No mutex here, setting a bool is probably atomic!
138     interruptRequested_ = true;
139 }
140 
141 // This is the thread's main loop
142 void LogDataWorkerThread::run()
143 {
144     QMutexLocker locker( &mutex_ );
145 
146     forever {
147         while ( (terminate_ == false) && (operationRequested_ == NULL) )
148             operationRequestedCond_.wait( &mutex_ );
149         LOG(logDEBUG) << "Worker thread signaled";
150 
151         // Look at what needs to be done
152         if ( terminate_ )
153             return;      // We must die
154 
155         if ( operationRequested_ ) {
156             connect( operationRequested_, SIGNAL( indexingProgressed( int ) ),
157                     this, SIGNAL( indexingProgressed( int ) ) );
158 
159             // Run the operation
160             try {
161                 if ( operationRequested_->start() ) {
162                     LOG(logDEBUG) << "... finished copy in workerThread.";
163                     emit indexingFinished( LoadingStatus::Successful );
164                 }
165                 else {
166                     emit indexingFinished( LoadingStatus::Interrupted );
167                 }
168             }
169             catch ( std::bad_alloc& ba ) {
170                 LOG(logERROR) << "Out of memory whilst indexing!";
171                 emit indexingFinished( LoadingStatus::NoMemory );
172             }
173 
174             delete operationRequested_;
175             operationRequested_ = NULL;
176             nothingToDoCond_.wakeAll();
177         }
178     }
179 }
180 
181 //
182 // Operations implementation
183 //
184 
185 IndexOperation::IndexOperation( const QString& fileName,
186        IndexingData* indexingData, bool* interruptRequest )
187     : fileName_( fileName )
188 {
189     interruptRequest_ = interruptRequest;
190     indexing_data_ = indexingData;
191 }
192 
193 PartialIndexOperation::PartialIndexOperation( const QString& fileName,
194         IndexingData* indexingData, bool* interruptRequest, qint64 position )
195     : IndexOperation( fileName, indexingData, interruptRequest )
196 {
197     initialPosition_ = position;
198 }
199 
200 void IndexOperation::doIndex( IndexingData* indexing_data, qint64 initialPosition )
201 {
202     qint64 pos = initialPosition; // Absolute position of the start of current line
203     qint64 end = 0;               // Absolute position of the end of current line
204     int additional_spaces = 0;    // Additional spaces due to tabs
205 
206     QFile file( fileName_ );
207     if ( file.open( QIODevice::ReadOnly ) ) {
208         // Count the number of lines and max length
209         // (read big chunks to speed up reading from disk)
210         file.seek( pos );
211         while ( !file.atEnd() ) {
212             FastLinePositionArray line_positions;
213             int max_length = 0;
214 
215             if ( *interruptRequest_ )   // a bool is always read/written atomically isn't it?
216                 break;
217 
218             // Read a chunk of 5MB
219             const qint64 block_beginning = file.pos();
220             const QByteArray block = file.read( sizeChunk );
221 
222             // Count the number of lines in each chunk
223             qint64 pos_within_block = 0;
224             while ( pos_within_block != -1 ) {
225                 pos_within_block = qMax( pos - block_beginning, 0LL);
226                 // Looking for the next \n, expanding tabs in the process
227                 do {
228                     if ( pos_within_block < block.length() ) {
229                         const char c = block.at(pos_within_block);
230                         if ( c == '\n' )
231                             break;
232                         else if ( c == '\t' )
233                             additional_spaces += AbstractLogData::tabStop -
234                                 ( ( ( block_beginning - pos ) + pos_within_block
235                                     + additional_spaces ) % AbstractLogData::tabStop ) - 1;
236 
237                         pos_within_block++;
238                     }
239                     else {
240                         pos_within_block = -1;
241                     }
242                 } while ( pos_within_block != -1 );
243 
244                 // When a end of line has been found...
245                 if ( pos_within_block != -1 ) {
246                     end = pos_within_block + block_beginning;
247                     const int length = end-pos + additional_spaces;
248                     if ( length > max_length )
249                         max_length = length;
250                     pos = end + 1;
251                     additional_spaces = 0;
252                     line_positions.append( pos );
253                 }
254             }
255 
256             // Update the shared data
257             indexing_data->addAll( block.length(), max_length, line_positions );
258 
259             // Update the caller for progress indication
260             int progress = ( file.size() > 0 ) ? pos*100 / file.size() : 100;
261             emit indexingProgressed( progress );
262         }
263 
264         // Check if there is a non LF terminated line at the end of the file
265         qint64 file_size = file.size();
266         if ( !*interruptRequest_ && file_size > pos ) {
267             LOG( logWARNING ) <<
268                 "Non LF terminated file, adding a fake end of line";
269 
270             FastLinePositionArray line_position;
271             line_position.append( file_size + 1 );
272             line_position.setFakeFinalLF();
273 
274             indexing_data->addAll( 0, 0, line_position );
275         }
276     }
277     else {
278         // TODO: Check that the file is seekable?
279         // If the file cannot be open, we do as if it was empty
280         LOG(logWARNING) << "Cannot open file " << fileName_.toStdString();
281 
282         emit indexingProgressed( 100 );
283     }
284 }
285 
286 // Called in the worker thread's context
287 bool FullIndexOperation::start()
288 {
289     LOG(logDEBUG) << "FullIndexOperation::start(), file "
290         << fileName_.toStdString();
291 
292     LOG(logDEBUG) << "FullIndexOperation: Starting the count...";
293 
294     emit indexingProgressed( 0 );
295 
296     // First empty the index
297     indexing_data_->clear();
298 
299     doIndex( indexing_data_, 0 );
300 
301     LOG(logDEBUG) << "FullIndexOperation: ... finished counting."
302         "interrupt = " << *interruptRequest_;
303 
304     return ( *interruptRequest_ ? false : true );
305 }
306 
307 bool PartialIndexOperation::start()
308 {
309     LOG(logDEBUG) << "PartialIndexOperation::start(), file "
310         << fileName_.toStdString();
311 
312     LOG(logDEBUG) << "PartialIndexOperation: Starting the count at "
313         << initialPosition_ << " ...";
314 
315     emit indexingProgressed( 0 );
316 
317     doIndex( indexing_data_, initialPosition_ );
318 
319     LOG(logDEBUG) << "PartialIndexOperation: ... finished counting.";
320 
321     return ( *interruptRequest_ ? false : true );
322 }
323