~ubuntu-branches/ubuntu/quantal/kiten/quantal-proposed

« back to all changes in this revision

Viewing changes to app/dictionaryupdatemanager.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2011-12-16 13:14:44 UTC
  • mfrom: (1.1.4)
  • Revision ID: package-import@ubuntu.com-20111216131444-fxt8pt2pha54qmdu
Tags: 4:4.7.90-0ubuntu1
New upstream beta release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*****************************************************************************
 
2
 * This file is part of Kiten, a KDE Japanese Reference Tool...              *
 
3
 * Copyright (C) 2011 Daniel E. Moctezuma <democtezuma@gmail.com>            *
 
4
 *                                                                           *
 
5
 * This program is free software; you can redistribute it and/or modify      *
 
6
 * it under the terms of the GNU General Public License as published by      *
 
7
 * the Free Software Foundation; either version 2 of the License, or         *
 
8
 * (at your option) any later version.                                       *
 
9
 *                                                                           *
 
10
 * This program is distributed in the hope that it will be useful,           *
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 
13
 * GNU General Public License for more details.                              *
 
14
 *                                                                           *
 
15
 * You should have received a copy of the GNU General Public License         *
 
16
 * along with this program; if not, write to the Free Software               *
 
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 *
 
18
 * USA                                                                       *
 
19
 *****************************************************************************/
 
20
 
 
21
#include "dictionaryupdatemanager.h"
 
22
 
 
23
#include "kiten.h"
 
24
#include "kitenconfig.h"
 
25
#include "kitenmacros.h"
 
26
 
 
27
#include <KAction>
 
28
#include <KActionCollection>
 
29
#include <KDebug>
 
30
#include <KFilterDev>
 
31
#include <KIO/Scheduler>
 
32
#include <KIO/StoredTransferJob>
 
33
#include <KLocale>
 
34
#include <KMessageBox>
 
35
#include <KStandardDirs>
 
36
#include <KUrl>
 
37
 
 
38
#include <QTemporaryFile>
 
39
#include <QTextCodec>
 
40
#include <QTextStream>
 
41
 
 
42
// URL to the information file.
 
43
#define INFO_URL     "http://ftp.monash.edu.au/pub/nihongo/edicthdr.txt"
 
44
// URL to the EDICT dictionary.
 
45
#define EDICT_URL    "http://ftp.monash.edu.au/pub/nihongo/edict.gz"
 
46
// URL to the KANJIDIC dictionary.
 
47
#define KANJIDIC_URL "http://ftp.monash.edu.au/pub/nihongo/kanjidic.gz"
 
48
 
 
49
DictionaryUpdateManager::DictionaryUpdateManager( Kiten *parent )
 
50
: QObject( parent )
 
51
, _parent( parent )
 
52
, _config( parent->getConfig() )
 
53
, _succeeded( QStringList() )
 
54
, _failed( QStringList() )
 
55
, _counter( 0 )
 
56
{
 
57
  _actionUpdate = _parent->actionCollection()->add<KAction>( "update_dictionaries" );
 
58
  _actionUpdate->setText( i18n( "Check for dictionary &updates" ) );
 
59
  _actionUpdate->setShortcut( Qt::CTRL+Qt::Key_U );
 
60
 
 
61
  connect( _actionUpdate, SIGNAL( triggered() ),
 
62
                    this,   SLOT( checkForUpdates() ) );
 
63
}
 
64
 
 
65
void DictionaryUpdateManager::checkForUpdates()
 
66
{
 
67
  kDebug() << "Checking for EDICT & KANJIDIC updates." << endl;
 
68
  // Download the information file we need to check
 
69
  // whether or not an update to our dictionaries is necessary.
 
70
  KIO::StoredTransferJob *job = KIO::storedGet( KUrl( INFO_URL ) );
 
71
  connect(  job, SIGNAL( result( KJob* ) ),
 
72
           this,   SLOT( checkInfoFile( KJob* ) ) );
 
73
}
 
74
 
 
75
void DictionaryUpdateManager::checkIfUpdateFinished()
 
76
{
 
77
  // Emit the updateFinished signal once we finished
 
78
  // to update our installed dictionaries.
 
79
  if( _counter == _config->dictionary_list().size() )
 
80
  {
 
81
    // Make sure to reset this variable to 0.
 
82
    _counter = 0;
 
83
    emit updateFinished();
 
84
  }
 
85
}
 
86
 
 
87
void DictionaryUpdateManager::checkInfoFile( KJob *job )
 
88
{
 
89
  KIO::StoredTransferJob *storedJob = static_cast<KIO::StoredTransferJob*>( job );
 
90
  QByteArray data( storedJob->data() );
 
91
  // Check if we have valid data to work with.
 
92
  if( data.isNull() || data.isEmpty() )
 
93
  {
 
94
    KMessageBox::sorry( 0, i18n( "Update canceled.\nCould not read file." ) );
 
95
    job->deleteLater();
 
96
    return;
 
97
  }
 
98
 
 
99
  // We don't need to store this information file in our Hard Disk Drive,
 
100
  // a temporary file is enough.
 
101
  QTemporaryFile tempFile;
 
102
  if( ! tempFile.open() )
 
103
  {
 
104
    KMessageBox::sorry( 0, i18n( "Update canceled.\nCould not open file." ) );
 
105
    kDebug() << "Could not open tempFile." << endl;
 
106
    tempFile.deleteLater();
 
107
    job->deleteLater();
 
108
    return;
 
109
  }
 
110
  tempFile.write( data );
 
111
  tempFile.close();
 
112
 
 
113
  // Get the latest creation date for the EDICT dictionary on the server.
 
114
  QDate webFileDate = getFileDate( tempFile );
 
115
  if( ! webFileDate.isValid() )
 
116
  {
 
117
    // The program should not get to this point.
 
118
    // Maybe the format/content of (one or both) the files changed?
 
119
    // or maybe one or both of the files could not be opened in the
 
120
    // getFileDate function that would return an invalid empty date.
 
121
    KMessageBox::sorry( 0, i18n( "Update canceled.\nThe update information file has an invalid date." ) );
 
122
    tempFile.deleteLater();
 
123
    job->deleteLater();
 
124
    return;
 
125
  }
 
126
 
 
127
  // At this point we have a valid creation date from the server.
 
128
  // Now we can check our dictionaries.
 
129
 
 
130
  // Connect to this signal to know when the update process is finished.
 
131
  connect( this, SIGNAL( updateFinished() ),
 
132
           this,   SLOT( showUpdateResults() ) );
 
133
 
 
134
  // This variable help us to know if we need to download any file.
 
135
  bool updates = false;
 
136
  // Iterate on each of our installed dictionaries.
 
137
  foreach( const QString &dict, _config->dictionary_list() )
 
138
  {
 
139
    QString filePath = KGlobal::dirs()->findResource( "data", QString( "kiten/" ) + dict.toLower() );
 
140
    QFile file( filePath );
 
141
    kDebug() << "Local dictionary path:" << file.fileName() << endl;
 
142
 
 
143
    // Get the creation date for this dictionary.
 
144
    QDate localFileDate = getFileDate( file );
 
145
 
 
146
    if( ! localFileDate.isValid() )
 
147
    {
 
148
      // Add it to our 'failed to udate' list.
 
149
      _failed.append( dict.toUpper() );
 
150
      kDebug() << "Failed (invalid date):" << dict.toUpper() << endl;
 
151
      continue;
 
152
    }
 
153
    else if( localFileDate == webFileDate )
 
154
    {
 
155
      // Add it to our 'up to date' list.
 
156
      _succeeded.append( dict.toUpper() );
 
157
      kDebug() << "Success (up to date):" << dict.toUpper() << endl;
 
158
      continue;
 
159
    }
 
160
    else
 
161
    {
 
162
      // Indicate we need to download something.
 
163
      updates = true;
 
164
 
 
165
      if( dict.toLower() == EDICT )
 
166
      {
 
167
        downloadDictionary( EDICT_URL );
 
168
      }
 
169
      else
 
170
      {
 
171
        downloadDictionary( KANJIDIC_URL );
 
172
      }
 
173
    }
 
174
  }
 
175
 
 
176
  // Let Kiten know we finished and don't need to download anything else.
 
177
  if( ! updates )
 
178
  {
 
179
    emit updateFinished();
 
180
  }
 
181
 
 
182
  tempFile.deleteLater();
 
183
  job->deleteLater();
 
184
}
 
185
 
 
186
void DictionaryUpdateManager::downloadDictionary( const QString &url )
 
187
{
 
188
  kDebug() << "Download started!" << endl;
 
189
  // Download dictionary.
 
190
  KIO::StoredTransferJob *dictionaryJob = KIO::storedGet( KUrl( url ) );
 
191
  connect( dictionaryJob, SIGNAL( result( KJob* ) ),
 
192
                    this,   SLOT( installDictionary( KJob* ) ) );
 
193
}
 
194
 
 
195
QDate DictionaryUpdateManager::getFileDate( QFile &file )
 
196
{
 
197
  if( ! file.open( QIODevice::ReadOnly | QIODevice::Text ) )
 
198
  {
 
199
    kDebug() << "Could not open " << file.fileName() << endl;
 
200
    return QDate();
 
201
  }
 
202
 
 
203
  QTextStream fileStream( &file );
 
204
  fileStream.setCodec( QTextCodec::codecForName( "eucJP" ) );
 
205
 
 
206
  // The first line of the file is in the following form:
 
207
  //  ??? /
 
208
  // EDICT, EDICT_SUB(P), EDICT2 Japanese-English Electronic Dictionary Files/
 
209
  // Copyright Electronic Dictionary Research & Development Group - year/
 
210
  // Created: year-month-day/
 
211
  // ###### entries
 
212
 
 
213
  // NOTE: the above last line only appears in the web status file for EDICT.
 
214
  // That file has only 2 lines, one is the '?' simbols with the name of the
 
215
  // dictionary, copyright and creation date, the other one is the number
 
216
  // of entries in the EDICT dictionary.
 
217
  // (see INFO_URL macro).
 
218
 
 
219
  // We take the 4th section which is the last one
 
220
  // separated by the '/' character and the
 
221
  // 10 rightmost characters for that section.
 
222
  QString dateSection = fileStream.readLine()
 
223
                                  .section( '/', -1, -1, QString::SectionSkipEmpty )
 
224
                                  .right( 10 );
 
225
 
 
226
  // dateSection has the following value: year-month-day
 
227
  // Finally we take the numbers separated by the '-' character.
 
228
  int year  = dateSection.section( '-', 0, 0 ).toInt();
 
229
  int month = dateSection.section( '-', 1, 1 ).toInt();
 
230
  int day   = dateSection.section( '-', 2, 2 ).toInt();
 
231
 
 
232
  file.close();
 
233
 
 
234
  kDebug() << "Date found:" << dateSection << "(" << file.fileName() << ")" << endl;
 
235
 
 
236
  return QDate( year, month, day );
 
237
}
 
238
 
 
239
void DictionaryUpdateManager::installDictionary( KJob *job )
 
240
{
 
241
  // Increase the number of dictionaries we try to install.
 
242
  // This way we can know when we finished with our installed dictionaries.
 
243
  _counter++;
 
244
 
 
245
  KIO::StoredTransferJob *storedJob = static_cast<KIO::StoredTransferJob*>( job );
 
246
  QByteArray data( storedJob->data() );
 
247
  QString url( storedJob->url().prettyUrl() );
 
248
 
 
249
  // What we actually downloaded was a GZIP file that we need to extract.
 
250
  // As there is no need to keep this file, we make it a temporary file.
 
251
  QTemporaryFile compressedFile;
 
252
  if( ! compressedFile.open() )
 
253
  {
 
254
    kDebug() << "Could not create the downloaded .gz file." << endl;
 
255
    _failed.append( url.contains( EDICT ) ? EDICT : KANJIDIC );
 
256
    job->deleteLater();
 
257
    checkIfUpdateFinished();
 
258
    return;
 
259
  }
 
260
  // Create the GZIP file from the downloaded data.
 
261
  compressedFile.write( data );
 
262
  compressedFile.close();
 
263
 
 
264
  kDebug() << "Dictionary download finished!" << endl;
 
265
  kDebug() << "Extracting dictionary..." << endl;
 
266
 
 
267
  // Extract the GZIP file.
 
268
  QIODevice *device = KFilterDev::deviceForFile( compressedFile.fileName(), "application/x-gzip" );
 
269
  if( ! device->open( QIODevice::ReadOnly ) )
 
270
  {
 
271
    kDebug() << "Could not extract the dictionary file." << endl;
 
272
    _failed.append( url.contains( EDICT ) ? EDICT : KANJIDIC );
 
273
    delete device;
 
274
    job->deleteLater();
 
275
    checkIfUpdateFinished();
 
276
    return;
 
277
  }
 
278
 
 
279
  // Check the first line's first character of the extracted file.
 
280
  // EDICT starts with a ' ', KANJIDIC starts with a '#'.
 
281
  QString fileName = device->readLine().startsWith( '#' ) ? QString( "kanjidic" ) : QString( "edict" );
 
282
  // Reset the position where we are going to start reading the content.
 
283
  device->reset();
 
284
  // Thanks to the above lines we can get the path to the correct file to be updated.
 
285
  QString dictPath = KGlobal::dirs()->locateLocal( "data"
 
286
                                                  , QString( "kiten/" ) + fileName
 
287
                                                  , true );
 
288
  QFile dictionary( dictPath );
 
289
  if( ! dictionary.open( QIODevice::WriteOnly ) )
 
290
  {
 
291
    kDebug() << "Could not create the new dictionary file." << endl;
 
292
    _failed.append( fileName.toUpper() );
 
293
    device->close();
 
294
    delete device;
 
295
    job->deleteLater();
 
296
    checkIfUpdateFinished();
 
297
    return;
 
298
  }
 
299
 
 
300
  // Write the new dictionary file to disk.
 
301
  dictionary.write( device->readAll() );
 
302
  dictionary.close();
 
303
  device->close();
 
304
 
 
305
  delete device;
 
306
  job->deleteLater();
 
307
 
 
308
  // Check if we finished updating.
 
309
  checkIfUpdateFinished();
 
310
  kDebug() << "Successfully installed at:" << dictionary.fileName() << endl;
 
311
}
 
312
 
 
313
void DictionaryUpdateManager::showUpdateResults()
 
314
{
 
315
  // Avoid multiple calls to this slot.
 
316
  disconnect( this, SIGNAL( updateFinished() ),
 
317
              this,   SLOT( showUpdateResults() ) );
 
318
  
 
319
  if( ! _succeeded.isEmpty() && _failed.isEmpty() )
 
320
  {
 
321
    KMessageBox::information( 0, i18n( "You already have the latest updates." ) );
 
322
  }
 
323
  else if( _succeeded.isEmpty() && _failed.isEmpty() )
 
324
  {
 
325
    KMessageBox::information( 0, i18n( "Successfully updated your dictionaries." ) );
 
326
  }
 
327
  else if( ! _succeeded.isEmpty() && ! _failed.isEmpty() )
 
328
  {
 
329
    KMessageBox::information( 0, i18n( "Successfully updated:\n%1\n\nFailed to update:\n%2"
 
330
                                      , _succeeded.join( "\n" )
 
331
                                      , _failed.join( "\n" ) ) );
 
332
  }
 
333
  else if( _succeeded.isEmpty() && ! _failed.isEmpty() )
 
334
  {
 
335
    KMessageBox::sorry( 0, i18n( "Failed to update:\n%1"
 
336
                                , _failed.join( "\n" ) ) );
 
337
  }
 
338
 
 
339
  // Avoid repetitions in our lists.
 
340
  _succeeded.clear();
 
341
  _failed.clear();
 
342
}
 
343
 
 
344
#include "dictionaryupdatemanager.moc"