File indexing completed on 2024-05-12 04:41:31
0001 /* This file is part of the KDE project 0002 Copyright (C) 2006-2013 Jarosław Staniek <staniek@kde.org> 0003 0004 This library is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; either 0007 version 2 of the License, or (at your option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 * Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "SqliteVacuum.h" 0021 #include "sqlite_debug.h" 0022 0023 #include "KDb.h" 0024 0025 #include <QMessageBox> 0026 #include <QProgressDialog> 0027 #include <QFileInfo> 0028 #include <QFile> 0029 #include <QDir> 0030 #include <QApplication> 0031 #include <QProcess> 0032 #include <QCursor> 0033 #include <QLocale> 0034 #include <QTemporaryFile> 0035 0036 namespace { 0037 #ifdef Q_OS_WIN 0038 #include <Windows.h> 0039 void usleep(unsigned int usec) 0040 { 0041 Sleep(usec/1000); 0042 } 0043 0044 //! @todo Use when it's in kdewin 0045 #define CONV(x) ((wchar_t*)x.utf16()) 0046 int atomic_rename(const QString &in, const QString &out) 0047 { 0048 // better than :waccess/_wunlink/_wrename 0049 # ifndef _WIN32_WCE 0050 bool ok = (MoveFileExW(CONV(in), CONV(out), 0051 MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0); 0052 # else 0053 bool ok = (MoveFileW(CONV(in), CONV(out)) != 0); 0054 # endif 0055 return ok ? 0 : -1; 0056 } 0057 #else 0058 #include <unistd.h> 0059 int atomic_rename(const QString &in, const QString &out) 0060 { 0061 return ::rename(QFile::encodeName(in).constData(), QFile::encodeName(out).constData()); 0062 } 0063 #endif 0064 } // namespace 0065 0066 SqliteVacuum::SqliteVacuum(const QString& filePath) 0067 : m_filePath(filePath) 0068 { 0069 m_dumpProcess = nullptr; 0070 m_sqliteProcess = nullptr; 0071 m_percent = 0; 0072 m_dlg = nullptr; 0073 m_canceled = false; 0074 } 0075 0076 SqliteVacuum::~SqliteVacuum() 0077 { 0078 if (m_dumpProcess) { 0079 m_dumpProcess->waitForFinished(); 0080 delete m_dumpProcess; 0081 } 0082 if (m_sqliteProcess) { 0083 m_sqliteProcess->waitForFinished(); 0084 delete m_sqliteProcess; 0085 } 0086 if (m_dlg) 0087 m_dlg->reset(); 0088 delete m_dlg; 0089 QFile::remove(m_tmpFilePath); 0090 } 0091 0092 tristate SqliteVacuum::run() 0093 { 0094 const QString dump_app = QString::fromLatin1(KDB_SQLITE_DUMP_TOOL); 0095 //sqliteDebug() << dump_app; 0096 if (dump_app.isEmpty()) { 0097 m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not find tool \"%1\".") 0098 .arg(dump_app)); 0099 sqliteWarning() << m_result; 0100 return false; 0101 } 0102 const QString sqlite_app(KDb::sqlite3ProgramPath()); 0103 //sqliteDebug() << sqlite_app; 0104 if (sqlite_app.isEmpty()) { 0105 m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not find application \"%1\".") 0106 .arg(sqlite_app)); 0107 sqliteWarning() << m_result; 0108 return false; 0109 } 0110 0111 QFileInfo fi(m_filePath); 0112 if (!fi.isReadable()) { 0113 m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not read file \"%1\".") 0114 .arg(m_filePath)); 0115 sqliteWarning() << m_result; 0116 return false; 0117 } 0118 0119 //sqliteDebug() << fi.absoluteFilePath() << fi.absoluteDir().path(); 0120 0121 delete m_dumpProcess; 0122 m_dumpProcess = new QProcess(this); 0123 m_dumpProcess->setWorkingDirectory(fi.absoluteDir().path()); 0124 m_dumpProcess->setReadChannel(QProcess::StandardError); 0125 connect(m_dumpProcess, SIGNAL(readyReadStandardError()), this, SLOT(readFromStdErr())); 0126 connect(m_dumpProcess, SIGNAL(finished(int,QProcess::ExitStatus)), 0127 this, SLOT(dumpProcessFinished(int,QProcess::ExitStatus))); 0128 0129 delete m_sqliteProcess; 0130 m_sqliteProcess = new QProcess(this); 0131 m_sqliteProcess->setWorkingDirectory(fi.absoluteDir().path()); 0132 connect(m_sqliteProcess, SIGNAL(finished(int,QProcess::ExitStatus)), 0133 this, SLOT(sqliteProcessFinished(int,QProcess::ExitStatus))); 0134 0135 m_dumpProcess->setStandardOutputProcess(m_sqliteProcess); 0136 m_dumpProcess->start(dump_app, QStringList() << fi.absoluteFilePath()); 0137 if (!m_dumpProcess->waitForStarted()) { 0138 delete m_dumpProcess; 0139 m_dumpProcess = nullptr; 0140 m_result.setCode(ERR_OTHER); 0141 return false; 0142 } 0143 0144 { 0145 QTemporaryFile tempFile(fi.absoluteFilePath()); 0146 if (!tempFile.open()) { 0147 delete m_dumpProcess; 0148 m_dumpProcess = nullptr; 0149 m_result.setCode(ERR_OTHER); 0150 return false; 0151 } 0152 m_tmpFilePath = tempFile.fileName(); 0153 } 0154 //sqliteDebug() << m_tmpFilePath; 0155 m_sqliteProcess->start(sqlite_app, QStringList() << m_tmpFilePath); 0156 if (!m_sqliteProcess->waitForStarted()) { 0157 delete m_dumpProcess; 0158 m_dumpProcess = nullptr; 0159 delete m_sqliteProcess; 0160 m_sqliteProcess = nullptr; 0161 m_result.setCode(ERR_OTHER); 0162 return false; 0163 } 0164 0165 delete m_dlg; 0166 m_dlg = new QProgressDialog(nullptr); // krazy:exclude=qclasses 0167 m_dlg->setWindowModality(Qt::WindowModal); 0168 m_dlg->setWindowTitle(tr("Compacting database")); 0169 m_dlg->setLabelText( 0170 QLatin1String("<qt>") + tr("Compacting database \"%1\"...") 0171 .arg(QLatin1String("<nobr>") 0172 + QDir::fromNativeSeparators(fi.fileName()) 0173 + QLatin1String("</nobr>")) 0174 ); 0175 m_dlg->adjustSize(); 0176 m_dlg->resize(300, m_dlg->height()); 0177 m_dlg->setMinimumDuration(1000); 0178 m_dlg->setAutoClose(true); 0179 m_dlg->setRange(0, 100); 0180 m_dlg->exec(); 0181 if (m_dlg->wasCanceled()) { 0182 cancelClicked(); 0183 } 0184 delete m_dlg; 0185 m_dlg = nullptr; 0186 while (m_dumpProcess->state() == QProcess::Running 0187 && m_sqliteProcess->state() == QProcess::Running) 0188 { 0189 readFromStdErr(); 0190 qApp->processEvents(QEventLoop::AllEvents, 50000); 0191 } 0192 0193 readFromStdErr(); 0194 return !m_result.isError(); 0195 } 0196 0197 void SqliteVacuum::readFromStdErr() 0198 { 0199 while (true) { 0200 QByteArray s(m_dumpProcess->readLine(1000)); 0201 if (s.isEmpty()) 0202 break; 0203 //sqliteDebug() << s; 0204 if (s.startsWith("DUMP: ")) { 0205 //set previously known progress 0206 if (m_dlg) { 0207 m_dlg->setValue(m_percent); 0208 } 0209 //update progress info 0210 if (s.mid(6, 4) == "100%") { 0211 m_percent = 100; 0212 //! @todo IMPORTANT: m_dlg->setAllowCancel(false); 0213 if (m_dlg) { 0214 m_dlg->setCursor(QCursor(Qt::WaitCursor)); 0215 } 0216 } else if (s.mid(7, 1) == "%") { 0217 m_percent = s.mid(6, 1).toInt(); 0218 } else if (s.mid(8, 1) == "%") { 0219 m_percent = s.mid(6, 2).toInt(); 0220 } 0221 if (m_dlg) { 0222 m_dlg->setValue(m_percent); 0223 } 0224 } 0225 } 0226 } 0227 0228 void SqliteVacuum::dumpProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) 0229 { 0230 //sqliteDebug() << exitCode << exitStatus; 0231 if (exitCode != 0 || exitStatus != QProcess::NormalExit) { 0232 cancelClicked(); 0233 m_result.setCode(ERR_OTHER); 0234 } 0235 } 0236 0237 void SqliteVacuum::sqliteProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) 0238 { 0239 //sqliteDebug() << exitCode << exitStatus; 0240 if (exitCode != 0 || exitStatus != QProcess::NormalExit) { 0241 m_result.setCode(ERR_OTHER); 0242 } 0243 0244 if (m_dlg) { 0245 m_dlg->reset(); 0246 } 0247 0248 if (m_result.isError() || m_canceled) { 0249 return; 0250 } 0251 0252 // dump process and sqlite process finished by now so we can rename the result to the original name 0253 QFileInfo fi(m_filePath); 0254 const qint64 origSize = fi.size(); 0255 0256 const QString newName(fi.absoluteFilePath()); 0257 if (0 != atomic_rename(m_tmpFilePath, newName)) { 0258 m_result= KDbResult(ERR_ACCESS_RIGHTS, 0259 tr("Could not rename file \"%1\" to \"%2\".").arg(m_tmpFilePath, newName)); 0260 sqliteWarning() << m_result; 0261 } 0262 0263 if (!m_result.isError()) { 0264 const qint64 newSize = QFileInfo(m_filePath).size(); 0265 const qint64 decrease = 100 - 100 * newSize / origSize; 0266 QMessageBox::information(nullptr, QString(), // krazy:exclude=qclasses 0267 tr("The database has been compacted. Current size decreased by %1% to %2 MB.") 0268 .arg(decrease).arg(QLocale().toString(double(newSize)/1000000.0, 'f', 2))); 0269 } 0270 } 0271 0272 void SqliteVacuum::cancelClicked() 0273 { 0274 m_sqliteProcess->terminate(); 0275 m_canceled = true; 0276 QFile::remove(m_tmpFilePath); 0277 }