File indexing completed on 2024-05-12 04:57:54

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "profilemanager.h"
0019 #include "mainapplication.h"
0020 #include "datapaths.h"
0021 #include "updater.h"
0022 #include "qztools.h"
0023 #include "sqldatabase.h"
0024 
0025 #include <QDir>
0026 #include <QSqlError>
0027 #include <QSqlQuery>
0028 #include <QSqlDatabase>
0029 #include <QMessageBox>
0030 #include <QSettings>
0031 #include <QStandardPaths>
0032 
0033 #include <iostream>
0034 
0035 ProfileManager::ProfileManager()
0036 = default;
0037 
0038 void ProfileManager::initConfigDir()
0039 {
0040     QDir dir(DataPaths::path(DataPaths::Config));
0041 
0042     if (!dir.exists()) {
0043         migrateFromQupZilla();
0044     }
0045 
0046     if (QFileInfo::exists(dir.filePath(QStringLiteral("profiles/profiles.ini")))) {
0047         return;
0048     }
0049 
0050     std::cout << "Falkon: Creating new profile directory" << std::endl;
0051 
0052     if (!dir.exists()) {
0053         dir.mkpath(dir.absolutePath());
0054     }
0055 
0056     dir.mkdir(QStringLiteral("profiles"));
0057     dir.cd(QStringLiteral("profiles"));
0058 
0059     // $Config/profiles
0060     QFile(dir.filePath(QStringLiteral("profiles.ini"))).remove();
0061     QFile(QStringLiteral(":data/profiles.ini")).copy(dir.filePath(QStringLiteral("profiles.ini")));
0062     QFile(dir.filePath(QStringLiteral("profiles.ini"))).setPermissions(QFile::ReadUser | QFile::WriteUser);
0063 
0064     dir.mkdir(QStringLiteral("default"));
0065     dir.cd(QStringLiteral("default"));
0066 
0067     // $Config/profiles/default
0068     QFile(QStringLiteral(":data/bookmarks.json")).copy(dir.filePath(QStringLiteral("bookmarks.json")));
0069     QFile(dir.filePath(QStringLiteral("bookmarks.json"))).setPermissions(QFile::ReadUser | QFile::WriteUser);
0070 
0071     QFile versionFile(dir.filePath(QStringLiteral("version")));
0072     versionFile.open(QFile::WriteOnly);
0073     versionFile.write(Qz::VERSION);
0074     versionFile.close();
0075 }
0076 
0077 void ProfileManager::initCurrentProfile(const QString &profileName)
0078 {
0079     QString profilePath = DataPaths::path(DataPaths::Profiles) + QLatin1Char('/');
0080 
0081     if (profileName.isEmpty()) {
0082         profilePath.append(startingProfile());
0083     }
0084     else {
0085         profilePath.append(profileName);
0086     }
0087 
0088     DataPaths::setCurrentProfilePath(profilePath);
0089 
0090     updateCurrentProfile();
0091     connectDatabase();
0092 }
0093 
0094 int ProfileManager::createProfile(const QString &profileName)
0095 {
0096     QDir dir(DataPaths::path(DataPaths::Profiles));
0097 
0098     if (QDir(dir.absolutePath() + QLatin1Char('/') + profileName).exists()) {
0099         return -1;
0100     }
0101     if (!dir.mkdir(profileName)) {
0102         return -2;
0103     }
0104 
0105     dir.cd(profileName);
0106 
0107     QFile versionFile(dir.filePath(QStringLiteral("version")));
0108     versionFile.open(QFile::WriteOnly);
0109     versionFile.write(Qz::VERSION);
0110     versionFile.close();
0111 
0112     return 0;
0113 }
0114 
0115 bool ProfileManager::removeProfile(const QString &profileName)
0116 {
0117     QDir dir(DataPaths::path(DataPaths::Profiles) + QLatin1Char('/') + profileName);
0118 
0119     if (!dir.exists()) {
0120         return false;
0121     }
0122 
0123     QzTools::removeRecursively(dir.absolutePath());
0124     return true;
0125 }
0126 
0127 // static
0128 QString ProfileManager::currentProfile()
0129 {
0130     QString path = DataPaths::currentProfilePath();
0131     return path.mid(path.lastIndexOf(QLatin1Char('/')) + 1);
0132 }
0133 
0134 // static
0135 QString ProfileManager::startingProfile()
0136 {
0137     QSettings settings(DataPaths::path(DataPaths::Profiles) + QLatin1String("/profiles.ini"), QSettings::IniFormat);
0138     return settings.value(QStringLiteral("Profiles/startProfile"), QLatin1String("default")).toString();
0139 }
0140 
0141 // static
0142 void ProfileManager::setStartingProfile(const QString &profileName)
0143 {
0144     QSettings settings(DataPaths::path(DataPaths::Profiles) + QLatin1String("/profiles.ini"), QSettings::IniFormat);
0145     settings.setValue(QStringLiteral("Profiles/startProfile"), profileName);
0146 }
0147 
0148 // static
0149 QStringList ProfileManager::availableProfiles()
0150 {
0151     QDir dir(DataPaths::path(DataPaths::Profiles));
0152     return dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0153 }
0154 
0155 void ProfileManager::updateCurrentProfile()
0156 {
0157     QDir profileDir(DataPaths::currentProfilePath());
0158 
0159     if (!profileDir.exists()) {
0160         QDir newDir(profileDir.path().remove(profileDir.dirName()));
0161         newDir.mkdir(profileDir.dirName());
0162     }
0163 
0164     QFile versionFile(profileDir.filePath(QStringLiteral("version")));
0165 
0166     // If file exists, just update the profile to current version
0167     if (versionFile.exists()) {
0168         versionFile.open(QFile::ReadOnly);
0169         QString profileVersion = QString::fromUtf8(versionFile.readAll());
0170         versionFile.close();
0171 
0172         updateProfile(QString::fromLatin1(Qz::VERSION), profileVersion.trimmed());
0173     }
0174     else {
0175         copyDataToProfile();
0176     }
0177 
0178     versionFile.open(QFile::WriteOnly);
0179     versionFile.write(Qz::VERSION);
0180     versionFile.close();
0181 }
0182 
0183 void ProfileManager::updateProfile(const QString &current, const QString &profile)
0184 {
0185     if (current == profile) {
0186         return;
0187     }
0188 
0189     Updater::Version prof(profile);
0190 
0191     // Profile is from newer version than running application
0192     if (prof > Updater::Version(QString::fromLatin1(Qz::VERSION))) {
0193         // Only copy data when profile is not from development version
0194         if (prof.revisionNumber != 99) {
0195             copyDataToProfile();
0196         }
0197         return;
0198     }
0199 
0200     if (prof < Updater::Version(QStringLiteral("1.9.0"))) {
0201         std::cout << "Falkon: Using profile from QupZilla " << qPrintable(profile) << " is not supported!" << std::endl;
0202         return;
0203     }
0204 
0205     // No change in 2.0
0206     if (prof < Updater::Version(QStringLiteral("2.9.99"))) {
0207         return;
0208     }
0209 
0210     // No change in 3.0
0211     if (prof < Updater::Version(QStringLiteral("3.0.99"))) {
0212         return;
0213     }
0214 
0215     // No change in 3.1
0216     if (prof < Updater::Version(QStringLiteral("3.1.99"))) {
0217         return;
0218     }
0219 }
0220 
0221 void ProfileManager::copyDataToProfile()
0222 {
0223     QDir profileDir(DataPaths::currentProfilePath());
0224 
0225     QFile browseData(profileDir.filePath(QStringLiteral("browsedata.db")));
0226 
0227     if (browseData.exists()) {
0228         const QString browseDataBackup = QzTools::ensureUniqueFilename(profileDir.filePath(QStringLiteral("browsedata-backup.db")));
0229         browseData.copy(browseDataBackup);
0230         browseData.remove();
0231 
0232         QFile settings(profileDir.filePath(QSL("settings.ini")));
0233         if (settings.exists()) {
0234             const QString settingsBackup = QzTools::ensureUniqueFilename(profileDir.filePath(QSL("settings-backup.ini")));
0235             settings.copy(settingsBackup);
0236             settings.remove();
0237         }
0238 
0239         QFile sessionFile(profileDir.filePath(QSL("session.dat")));
0240         if (sessionFile.exists()) {
0241             QString oldVersion = QzTools::readAllFileContents(profileDir.filePath(QSL("version"))).trimmed();
0242             if (oldVersion.isEmpty()) {
0243                 oldVersion = QSL("unknown-version");
0244             }
0245             const QString sessionBackup = QzTools::ensureUniqueFilename(profileDir.filePath(QSL("sessions/backup-%1.dat").arg(oldVersion)));
0246             sessionFile.copy(sessionBackup);
0247             sessionFile.remove();
0248         }
0249 
0250         const QString text = QSL("Incompatible profile version has been detected. To avoid losing your profile data, they were "
0251                              "backed up in following file:<br/><br/><b>") + browseDataBackup + QSL("<br/></b>");
0252         QMessageBox::warning(nullptr, QStringLiteral("Falkon: Incompatible profile version"), text);
0253     }
0254 }
0255 
0256 void ProfileManager::migrateFromQupZilla()
0257 {
0258     if (mApp->isPortable()) {
0259         return;
0260     }
0261 
0262 #if defined(Q_OS_WIN)
0263     const QString qzConfig = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QL1S("/qupzilla");
0264 #elif defined(Q_OS_MACOS)
0265     const QString qzConfig = QDir::homePath() + QLatin1String("/Library/Application Support/QupZilla");
0266 #else // Unix
0267     const QString qzConfig = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QL1S("/qupzilla");
0268 #endif
0269 
0270     if (!QFileInfo::exists(qzConfig)) {
0271         return;
0272     }
0273 
0274     std::cout << "Falkon: Migrating config from QupZilla..." << std::endl;
0275 
0276     QzTools::copyRecursively(qzConfig, DataPaths::path(DataPaths::Config));
0277 }
0278 
0279 void ProfileManager::connectDatabase()
0280 {
0281     QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"));
0282     if (!db.isValid()) {
0283         qCritical() << "Qt sqlite database driver is missing! Continuing without database....";
0284         return;
0285     }
0286 
0287     if (mApp->isPrivate()) {
0288         db.setConnectOptions(QStringLiteral("QSQLITE_OPEN_READONLY"));
0289     }
0290 
0291     db.setDatabaseName(DataPaths::currentProfilePath() + QLatin1String("/browsedata.db"));
0292 
0293     if (!db.open()) {
0294         qCritical() << "Cannot open SQLite database! Continuing without database....";
0295         return;
0296     }
0297 
0298     if (db.tables().isEmpty()) {
0299         const QStringList statements = QzTools::readAllFileContents(QSL(":/data/browsedata.sql")).split(QL1C(';'));
0300         for (const QString &statement : statements) {
0301             const QString stmt = statement.trimmed();
0302             if (stmt.isEmpty()) {
0303                 continue;
0304             }
0305             QSqlQuery query;
0306             if (!query.exec(stmt)) {
0307                 qCritical() << "Error creating database schema" << query.lastError().text();
0308             }
0309         }
0310     }
0311 
0312     SqlDatabase::instance()->setDatabase(db);
0313 }