KDb

SqliteVacuum.cpp
1 /* This file is part of the KDE project
2  Copyright (C) 2006-2013 JarosÅ‚aw Staniek <[email protected]>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later version.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18 */
19 
20 #include "SqliteVacuum.h"
21 #include "sqlite_debug.h"
22 
23 #include "KDb.h"
24 
25 #include <QMessageBox>
26 #include <QProgressDialog>
27 #include <QFileInfo>
28 #include <QFile>
29 #include <QDir>
30 #include <QApplication>
31 #include <QProcess>
32 #include <QCursor>
33 #include <QLocale>
34 #include <QTemporaryFile>
35 
36 namespace {
37 #ifdef Q_OS_WIN
38 #include <Windows.h>
39 void usleep(unsigned int usec)
40 {
41  Sleep(usec/1000);
42 }
43 
44 //! @todo Use when it's in kdewin
45 #define CONV(x) ((wchar_t*)x.utf16())
46 int atomic_rename(const QString &in, const QString &out)
47 {
48  // better than :waccess/_wunlink/_wrename
49 # ifndef _WIN32_WCE
50  bool ok = (MoveFileExW(CONV(in), CONV(out),
51  MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0);
52 # else
53  bool ok = (MoveFileW(CONV(in), CONV(out)) != 0);
54 # endif
55  return ok ? 0 : -1;
56 }
57 #else
58 #include <unistd.h>
59 int atomic_rename(const QString &in, const QString &out)
60 {
61  return ::rename(QFile::encodeName(in).constData(), QFile::encodeName(out).constData());
62 }
63 #endif
64 } // namespace
65 
66 SqliteVacuum::SqliteVacuum(const QString& filePath)
67  : m_filePath(filePath)
68 {
69  m_dumpProcess = nullptr;
70  m_sqliteProcess = nullptr;
71  m_percent = 0;
72  m_dlg = nullptr;
73  m_canceled = false;
74 }
75 
76 SqliteVacuum::~SqliteVacuum()
77 {
78  if (m_dumpProcess) {
79  m_dumpProcess->waitForFinished();
80  delete m_dumpProcess;
81  }
82  if (m_sqliteProcess) {
83  m_sqliteProcess->waitForFinished();
84  delete m_sqliteProcess;
85  }
86  if (m_dlg)
87  m_dlg->reset();
88  delete m_dlg;
89  QFile::remove(m_tmpFilePath);
90 }
91 
93 {
94  const QString dump_app = QString::fromLatin1(KDB_SQLITE_DUMP_TOOL);
95  //sqliteDebug() << dump_app;
96  if (dump_app.isEmpty()) {
97  m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not find tool \"%1\".")
98  .arg(dump_app));
99  sqliteWarning() << m_result;
100  return false;
101  }
102  const QString sqlite_app(KDb::sqlite3ProgramPath());
103  //sqliteDebug() << sqlite_app;
104  if (sqlite_app.isEmpty()) {
105  m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not find application \"%1\".")
106  .arg(sqlite_app));
107  sqliteWarning() << m_result;
108  return false;
109  }
110 
111  QFileInfo fi(m_filePath);
112  if (!fi.isReadable()) {
113  m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not read file \"%1\".")
114  .arg(m_filePath));
115  sqliteWarning() << m_result;
116  return false;
117  }
118 
119  //sqliteDebug() << fi.absoluteFilePath() << fi.absoluteDir().path();
120 
121  delete m_dumpProcess;
122  m_dumpProcess = new QProcess(this);
123  m_dumpProcess->setWorkingDirectory(fi.absoluteDir().path());
124  m_dumpProcess->setReadChannel(QProcess::StandardError);
125  connect(m_dumpProcess, SIGNAL(readyReadStandardError()), this, SLOT(readFromStdErr()));
126  connect(m_dumpProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
127  this, SLOT(dumpProcessFinished(int,QProcess::ExitStatus)));
128 
129  delete m_sqliteProcess;
130  m_sqliteProcess = new QProcess(this);
131  m_sqliteProcess->setWorkingDirectory(fi.absoluteDir().path());
132  connect(m_sqliteProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
133  this, SLOT(sqliteProcessFinished(int,QProcess::ExitStatus)));
134 
135  m_dumpProcess->setStandardOutputProcess(m_sqliteProcess);
136  m_dumpProcess->start(dump_app, QStringList() << fi.absoluteFilePath());
137  if (!m_dumpProcess->waitForStarted()) {
138  delete m_dumpProcess;
139  m_dumpProcess = nullptr;
140  m_result.setCode(ERR_OTHER);
141  return false;
142  }
143 
144  {
145  QTemporaryFile tempFile(fi.absoluteFilePath());
146  if (!tempFile.open()) {
147  delete m_dumpProcess;
148  m_dumpProcess = nullptr;
149  m_result.setCode(ERR_OTHER);
150  return false;
151  }
152  m_tmpFilePath = tempFile.fileName();
153  }
154  //sqliteDebug() << m_tmpFilePath;
155  m_sqliteProcess->start(sqlite_app, QStringList() << m_tmpFilePath);
156  if (!m_sqliteProcess->waitForStarted()) {
157  delete m_dumpProcess;
158  m_dumpProcess = nullptr;
159  delete m_sqliteProcess;
160  m_sqliteProcess = nullptr;
161  m_result.setCode(ERR_OTHER);
162  return false;
163  }
164 
165  delete m_dlg;
166  m_dlg = new QProgressDialog(nullptr); // krazy:exclude=qclasses
168  m_dlg->setWindowTitle(tr("Compacting database"));
169  m_dlg->setLabelText(
170  QLatin1String("<qt>") + tr("Compacting database \"%1\"...")
171  .arg(QLatin1String("<nobr>")
173  + QLatin1String("</nobr>"))
174  );
175  m_dlg->adjustSize();
176  m_dlg->resize(300, m_dlg->height());
177  m_dlg->setMinimumDuration(1000);
178  m_dlg->setAutoClose(true);
179  m_dlg->setRange(0, 100);
180  m_dlg->exec();
181  if (m_dlg->wasCanceled()) {
182  cancelClicked();
183  }
184  delete m_dlg;
185  m_dlg = nullptr;
186  while (m_dumpProcess->state() == QProcess::Running
187  && m_sqliteProcess->state() == QProcess::Running)
188  {
189  readFromStdErr();
190  qApp->processEvents(QEventLoop::AllEvents, 50000);
191  }
192 
193  readFromStdErr();
194  return !m_result.isError();
195 }
196 
198 {
199  while (true) {
200  QByteArray s(m_dumpProcess->readLine(1000));
201  if (s.isEmpty())
202  break;
203  //sqliteDebug() << s;
204  if (s.startsWith("DUMP: ")) {
205  //set previously known progress
206  if (m_dlg) {
207  m_dlg->setValue(m_percent);
208  }
209  //update progress info
210  if (s.mid(6, 4) == "100%") {
211  m_percent = 100;
212 //! @todo IMPORTANT: m_dlg->setAllowCancel(false);
213  if (m_dlg) {
215  }
216  } else if (s.mid(7, 1) == "%") {
217  m_percent = s.mid(6, 1).toInt();
218  } else if (s.mid(8, 1) == "%") {
219  m_percent = s.mid(6, 2).toInt();
220  }
221  if (m_dlg) {
222  m_dlg->setValue(m_percent);
223  }
224  }
225  }
226 }
227 
228 void SqliteVacuum::dumpProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
229 {
230  //sqliteDebug() << exitCode << exitStatus;
231  if (exitCode != 0 || exitStatus != QProcess::NormalExit) {
232  cancelClicked();
233  m_result.setCode(ERR_OTHER);
234  }
235 }
236 
237 void SqliteVacuum::sqliteProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
238 {
239  //sqliteDebug() << exitCode << exitStatus;
240  if (exitCode != 0 || exitStatus != QProcess::NormalExit) {
241  m_result.setCode(ERR_OTHER);
242  }
243 
244  if (m_dlg) {
245  m_dlg->reset();
246  }
247 
248  if (m_result.isError() || m_canceled) {
249  return;
250  }
251 
252  // dump process and sqlite process finished by now so we can rename the result to the original name
253  QFileInfo fi(m_filePath);
254  const qint64 origSize = fi.size();
255 
256  const QString newName(fi.absoluteFilePath());
257  if (0 != atomic_rename(m_tmpFilePath, newName)) {
258  m_result= KDbResult(ERR_ACCESS_RIGHTS,
259  tr("Could not rename file \"%1\" to \"%2\".").arg(m_tmpFilePath, newName));
260  sqliteWarning() << m_result;
261  }
262 
263  if (!m_result.isError()) {
264  const qint64 newSize = QFileInfo(m_filePath).size();
265  const qint64 decrease = 100 - 100 * newSize / origSize;
266  QMessageBox::information(nullptr, QString(), // krazy:exclude=qclasses
267  tr("The database has been compacted. Current size decreased by %1% to %2 MB.")
268  .arg(decrease).arg(QLocale().toString(double(newSize)/1000000.0, 'f', 2)));
269  }
270 }
271 
272 void SqliteVacuum::cancelClicked()
273 {
274  m_sqliteProcess->terminate();
275  m_canceled = true;
276  QFile::remove(m_tmpFilePath);
277 }
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QProcess::ProcessState state() const const
void adjustSize()
tristate run()
bool waitForFinished(int msecs)
void setWorkingDirectory(const QString &dir)
bool remove()
void setValue(int progress)
QByteArray encodeName(const QString &fileName)
WindowModal
void terminate()
void setLabelText(const QString &text)
void setRange(int minimum, int maximum)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
KDB_EXPORT QString sqlite3ProgramPath()
Definition: KDb.cpp:2082
virtual QString fileName() const const override
void setStandardOutputProcess(QProcess *destination)
QString absoluteFilePath() const const
char * toString(const T &value)
QByteArray mid(int pos, int len) const const
bool isEmpty() const const
WaitCursor
void setWindowModality(Qt::WindowModality windowModality)
bool waitForStarted(int msecs)
void setWindowTitle(const QString &)
void setAutoClose(bool close)
bool startsWith(const QByteArray &ba) const const
QString fromNativeSeparators(const QString &pathName)
qint64 size() const const
virtual int exec()
void setMinimumDuration(int ms)
bool isError() const
Definition: KDbResult.cpp:64
3-state logical type with three values: true, false and cancelled and convenient operators.
Definition: KDbTristate.h:100
QDir absoluteDir() const const
QString fileName() const const
qint64 readLine(char *data, qint64 maxSize)
QString path() const const
void readFromStdErr()
void resize(int w, int h)
int toInt(bool *ok, int base) const const
bool isEmpty() const const
QString fromLatin1(const char *str, int size)
void setReadChannel(QProcess::ProcessChannel channel)
QMessageBox::StandardButton information(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
QString tr(const char *sourceText, const char *disambiguation, int n)
bool isReadable() const const
void setCursor(const QCursor &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Jun 25 2022 06:21:34 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.