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());
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 {
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) {
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}
bool isError() const
Definition KDbResult.cpp:64
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
virtual int exec()
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
qint64 size() const const
QByteArray readLine(qint64 maxSize)
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)
void setReadChannel(ProcessChannel channel)
void setStandardOutputProcess(QProcess *destination)
void setWorkingDirectory(const QString &dir)
void start(OpenMode mode)
QProcess::ProcessState state() const const
void terminate()
bool waitForFinished(int msecs)
bool waitForStarted(int msecs)
void setAutoClose(bool close)
void setLabelText(const QString &text)
void setMinimumDuration(int ms)
void setRange(int minimum, int maximum)
void setValue(int progress)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
WaitCursor
WindowModal
virtual QString fileName() const const override
void adjustSize()
void setCursor(const QCursor &)
void resize(const QSize &)
void setWindowModality(Qt::WindowModality windowModality)
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 19 2024 12:00:07 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.