KDb

SqliteVacuum.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2006-2013 Jarosław Staniek <staniek@kde.org>
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
36namespace {
37#ifdef Q_OS_WIN
38#include <Windows.h>
39void 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())
46int 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>
59int 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
66SqliteVacuum::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
76SqliteVacuum::~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
167 m_dlg->setWindowModality(Qt::WindowModal);
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 {
190 qApp->processEvents(QEventLoop::AllEvents, 50000);
191 }
192
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) {
214 m_dlg->setCursor(QCursor(Qt::WaitCursor));
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
228void 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
237void 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
272void SqliteVacuum::cancelClicked()
273{
274 m_sqliteProcess->terminate();
275 m_canceled = true;
276 QFile::remove(m_tmpFilePath);
277}
tristate run()
void readFromStdErr()
3-state logical type with three values: true, false and cancelled and convenient operators.
char * toString(const EngineQuery &query)
KDB_EXPORT QString sqlite3ProgramPath()
bool isEmpty() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
bool startsWith(QByteArrayView bv) const const
int toInt(bool *ok, int base) const const
QString fromNativeSeparators(const QString &pathName)
QString path() const const
QByteArray encodeName(const QString &fileName)
bool remove()
QDir absoluteDir() const const
QString absoluteFilePath() const const
QString fileName() const const
bool isReadable() const const
StandardButton information(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString tr(const char *sourceText, const char *disambiguation, int n)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
WaitCursor
WindowModal
virtual QString fileName() const const override
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:48:12 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.