File indexing completed on 2024-05-12 04:58:21

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2017 Razi Alavizadeh <s.r.alavizadeh@gmail.com>
0004 * Copyright (C) 2018 David Rosca <nowrep@gmail.com>
0005 *
0006 * This program is free software: you can redistribute it and/or modify
0007 * it under the terms of the GNU General Public License as published by
0008 * the Free Software Foundation, either version 3 of the License, or
0009 * (at your option) any later version.
0010 *
0011 * This program is distributed in the hope that it will be useful,
0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 * GNU General Public License for more details.
0015 *
0016 * You should have received a copy of the GNU General Public License
0017 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0018 * ============================================================ */
0019 #include "browserwindow.h"
0020 #include "datapaths.h"
0021 #include "mainapplication.h"
0022 #include "restoremanager.h"
0023 #include "sessionmanager.h"
0024 #include "sessionmanagerdialog.h"
0025 #include "settings.h"
0026 
0027 #include <QAction>
0028 #include <QActionGroup>
0029 #include <QComboBox>
0030 #include <QDateTime>
0031 #include <QDialogButtonBox>
0032 #include <QDir>
0033 #include <QFileSystemWatcher>
0034 #include <QInputDialog>
0035 #include <QLabel>
0036 #include <QMenu>
0037 #include <QMessageBox>
0038 #include <QVBoxLayout>
0039 #include <QSaveFile>
0040 
0041 SessionManager::SessionManager(QObject* parent)
0042     : QObject(parent)
0043     , m_firstBackupSession(DataPaths::currentProfilePath() + QL1S("/session.dat.old"))
0044     , m_secondBackupSession(DataPaths::currentProfilePath() + QL1S("/session.dat.old1"))
0045 {
0046     auto* sessionFilesWatcher = new QFileSystemWatcher({DataPaths::path(DataPaths::Sessions)}, this);
0047     connect(sessionFilesWatcher, &QFileSystemWatcher::directoryChanged, this, &SessionManager::sessionsDirectoryChanged);
0048     connect(sessionFilesWatcher, &QFileSystemWatcher::directoryChanged, this, &SessionManager::sessionsMetaDataChanged);
0049 
0050     loadSettings();
0051 }
0052 
0053 void SessionManager::aboutToShowSessionsMenu()
0054 {
0055     auto* menu = qobject_cast<QMenu*>(sender());
0056     menu->clear();
0057 
0058     auto *group = new QActionGroup(menu);
0059 
0060     const auto sessions = sessionMetaData(/*withBackups*/ false);
0061     for (const SessionManager::SessionMetaData &metaData : sessions) {
0062         QAction* action = menu->addAction(metaData.name);
0063         action->setCheckable(true);
0064         action->setChecked(metaData.isActive);
0065         group->addAction(action);
0066         connect(action, &QAction::triggered, this, [=]() {
0067             switchToSession(metaData.filePath);
0068         });
0069     }
0070 }
0071 
0072 void SessionManager::sessionsDirectoryChanged()
0073 {
0074     m_sessionsMetaDataList.clear();
0075 }
0076 
0077 void SessionManager::openSession(QString sessionFilePath, SessionManager::SessionFlags flags)
0078 {
0079     if (sessionFilePath.isEmpty()) {
0080         auto* action = qobject_cast<QAction*>(sender());
0081         if (!action)
0082             return;
0083 
0084         sessionFilePath = action->data().toString();
0085     }
0086 
0087     if (isActive(sessionFilePath)) {
0088         return;
0089     }
0090 
0091     RestoreData sessionData;
0092     RestoreManager::createFromFile(sessionFilePath, sessionData);
0093 
0094     if (!sessionData.isValid())
0095         return;
0096 
0097     BrowserWindow* window = mApp->getWindow();
0098     if (flags.testFlag(SwitchSession)) {
0099         writeCurrentSession(m_lastActiveSessionPath);
0100 
0101         window = mApp->createWindow(Qz::BW_OtherRestoredWindow);
0102         for (BrowserWindow* win : mApp->windows()) {
0103             if (win != window)
0104                 win->close();
0105         }
0106 
0107         if (!flags.testFlag(ReplaceSession)) {
0108             m_lastActiveSessionPath = QFileInfo(sessionFilePath).canonicalFilePath();
0109             m_sessionsMetaDataList.clear();
0110         }
0111     }
0112 
0113     mApp->openSession(window, sessionData);
0114 }
0115 
0116 void SessionManager::renameSession(QString sessionFilePath, SessionManager::SessionFlags flags)
0117 {
0118     if (sessionFilePath.isEmpty()) {
0119         auto* action = qobject_cast<QAction*>(sender());
0120         if (!action)
0121             return;
0122 
0123         sessionFilePath = action->data().toString();
0124     }
0125 
0126     bool ok;
0127     const QString suggestedName = QFileInfo(sessionFilePath).completeBaseName() + (flags.testFlag(CloneSession) ? tr("_cloned") : tr("_renamed"));
0128     QString newName = QInputDialog::getText(mApp->activeWindow(), (flags.testFlag(CloneSession) ? tr("Clone Session") : tr("Rename Session")),
0129                                             tr("Please enter a new name:"), QLineEdit::Normal,
0130                                             suggestedName, &ok);
0131 
0132     if (!ok)
0133         return;
0134 
0135     const QString newSessionPath = QSL("%1/%2.dat").arg(DataPaths::path(DataPaths::Sessions), newName);
0136     if (QFile::exists(newSessionPath)) {
0137         QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("The session file \"%1\" exists. Please enter another name.").arg(newName));
0138         renameSession(sessionFilePath, flags);
0139         return;
0140     }
0141 
0142     if (flags.testFlag(CloneSession)) {
0143         if (!QFile::copy(sessionFilePath, newSessionPath)) {
0144             QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("An error occurred when cloning session file."));
0145             return;
0146         }
0147     } else {
0148         if (!QFile::rename(sessionFilePath, newSessionPath)) {
0149             QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("An error occurred when renaming session file."));
0150             return;
0151         }
0152         if (isActive(sessionFilePath)) {
0153             m_lastActiveSessionPath = newSessionPath;
0154             m_sessionsMetaDataList.clear();
0155         }
0156     }
0157 }
0158 
0159 void SessionManager::saveSession()
0160 {
0161     bool ok;
0162     QString sessionName = QInputDialog::getText(mApp->activeWindow(), tr("Save Session"),
0163                                          tr("Please enter a name to save session:"), QLineEdit::Normal,
0164                                          tr("Saved Session (%1)").arg(QDateTime::currentDateTime().toString(QSL("dd MMM yyyy HH-mm-ss"))), &ok);
0165 
0166     if (!ok)
0167         return;
0168 
0169     const QString filePath = QSL("%1/%2.dat").arg(DataPaths::path(DataPaths::Sessions), sessionName);
0170     if (QFile::exists(filePath)) {
0171         QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("The session file \"%1\" exists. Please enter another name.").arg(sessionName));
0172         saveSession();
0173         return;
0174     }
0175 
0176     writeCurrentSession(filePath);
0177 }
0178 
0179 void SessionManager::replaceSession(const QString &filePath)
0180 {
0181     QMessageBox::StandardButton result = QMessageBox::information(mApp->activeWindow(), tr("Restore Backup"), tr("Are you sure you want to replace current session?"),
0182                                                                   QMessageBox::Yes | QMessageBox::No);
0183     if (result == QMessageBox::Yes) {
0184         openSession(filePath, ReplaceSession);
0185     }
0186 }
0187 
0188 void SessionManager::switchToSession(const QString &filePath)
0189 {
0190     openSession(filePath, SwitchSession);
0191 }
0192 
0193 void SessionManager::cloneSession(const QString &filePath)
0194 {
0195     renameSession(filePath, CloneSession);
0196 }
0197 
0198 void SessionManager::deleteSession(const QString &filePath)
0199 {
0200     QMessageBox::StandardButton result = QMessageBox::information(mApp->activeWindow(), tr("Delete Session"), tr("Are you sure you want to delete session '%1'?")
0201                                                                   .arg(QFileInfo(filePath).completeBaseName()), QMessageBox::Yes | QMessageBox::No);
0202     if (result == QMessageBox::Yes) {
0203         QFile::remove(filePath);
0204     }
0205 }
0206 
0207 void SessionManager::newSession()
0208 {
0209     bool ok;
0210     QString sessionName = QInputDialog::getText(mApp->activeWindow(), tr("New Session"),
0211                                          tr("Please enter a name to create new session:"), QLineEdit::Normal,
0212                                          tr("New Session (%1)").arg(QDateTime::currentDateTime().toString(QSL("dd MMM yyyy HH-mm-ss"))), &ok);
0213 
0214     if (!ok)
0215         return;
0216 
0217     const QString filePath = QStringLiteral("%1/%2.dat").arg(DataPaths::path(DataPaths::Sessions), sessionName);
0218     if (QFile::exists(filePath)) {
0219         QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("The session file \"%1\" exists. Please enter another name.").arg(sessionName));
0220         newSession();
0221         return;
0222     }
0223 
0224     writeCurrentSession(m_lastActiveSessionPath);
0225 
0226     BrowserWindow* window = mApp->createWindow(Qz::BW_NewWindow);
0227     for (BrowserWindow* win : mApp->windows()) {
0228         if (win != window)
0229             win->close();
0230     }
0231 
0232     m_lastActiveSessionPath = filePath;
0233     autoSaveLastSession();
0234 }
0235 
0236 QList<SessionManager::SessionMetaData> SessionManager::sessionMetaData(bool withBackups)
0237 {
0238     fillSessionsMetaDataListIfNeeded();
0239 
0240     auto out = m_sessionsMetaDataList;
0241 
0242     if (withBackups && QFile::exists(m_firstBackupSession)) {
0243         SessionMetaData data;
0244         data.name = tr("Backup 1");
0245         data.filePath = m_firstBackupSession;
0246         data.isBackup = true;
0247         out.append(data);
0248     }
0249     if (withBackups && QFile::exists(m_secondBackupSession)) {
0250         SessionMetaData data;
0251         data.name = tr("Backup 2");
0252         data.filePath = m_secondBackupSession;
0253         data.isBackup = true;
0254         out.append(data);
0255     }
0256 
0257     return out;
0258 }
0259 
0260 bool SessionManager::isActive(const QString &filePath) const
0261 {
0262     return QFileInfo(filePath) == QFileInfo(m_lastActiveSessionPath);
0263 }
0264 
0265 bool SessionManager::isActive(const QFileInfo &fileInfo) const
0266 {
0267     return fileInfo == QFileInfo(m_lastActiveSessionPath);
0268 }
0269 
0270 void SessionManager::fillSessionsMetaDataListIfNeeded()
0271 {
0272     if (!m_sessionsMetaDataList.isEmpty())
0273         return;
0274 
0275     QDir dir(DataPaths::path(DataPaths::Sessions));
0276 
0277     const QFileInfoList sessionFiles = QFileInfoList() << QFileInfo(defaultSessionPath()) << dir.entryInfoList({QSL("*.*")}, QDir::Files, QDir::Time);
0278 
0279     QStringList fileNames;
0280 
0281     for (int i = 0; i < sessionFiles.size(); ++i) {
0282         const QFileInfo &fileInfo = sessionFiles.at(i);
0283 
0284         if (!RestoreManager::validateFile(fileInfo.absoluteFilePath()))
0285             continue;
0286 
0287         SessionMetaData metaData;
0288         metaData.name = fileInfo.completeBaseName();
0289 
0290         if (fileInfo == QFileInfo(defaultSessionPath())) {
0291             metaData.name = tr("Default Session");
0292             metaData.isDefault = true;
0293         } else if (fileNames.contains(fileInfo.completeBaseName())) {
0294             metaData.name = fileInfo.fileName();
0295         } else {
0296             metaData.name = fileInfo.completeBaseName();
0297         }
0298 
0299         if (isActive(fileInfo)) {
0300             metaData.isActive = true;
0301         }
0302 
0303         fileNames << metaData.name;
0304         metaData.filePath = fileInfo.canonicalFilePath();
0305 
0306         m_sessionsMetaDataList << metaData;
0307     }
0308 }
0309 
0310 void SessionManager::loadSettings()
0311 {
0312     QDir sessionsDir(DataPaths::path(DataPaths::Sessions));
0313 
0314     Settings settings;
0315     settings.beginGroup(QSL("Web-Browser-Settings"));
0316     m_lastActiveSessionPath = settings.value(QSL("lastActiveSessionPath"), defaultSessionPath()).toString();
0317     settings.endGroup();
0318 
0319     if (QDir::isRelativePath(m_lastActiveSessionPath)) {
0320         m_lastActiveSessionPath = sessionsDir.absoluteFilePath(m_lastActiveSessionPath);
0321     }
0322 
0323     // Fallback to default session
0324     if (!RestoreManager::validateFile(m_lastActiveSessionPath))
0325         m_lastActiveSessionPath = defaultSessionPath();
0326 }
0327 
0328 void SessionManager::saveSettings()
0329 {
0330     QDir sessionsDir(DataPaths::path(DataPaths::Sessions));
0331 
0332     Settings settings;
0333     settings.beginGroup(QSL("Web-Browser-Settings"));
0334     settings.setValue(QSL("lastActiveSessionPath"), sessionsDir.relativeFilePath(m_lastActiveSessionPath));
0335     settings.endGroup();
0336 }
0337 
0338 QString SessionManager::defaultSessionPath()
0339 {
0340     return DataPaths::currentProfilePath() + QL1S("/session.dat");
0341 }
0342 
0343 QString SessionManager::lastActiveSessionPath() const
0344 {
0345     return m_lastActiveSessionPath;
0346 }
0347 
0348 void SessionManager::backupSavedSessions()
0349 {
0350     if (!QFile::exists(m_lastActiveSessionPath)) {
0351         return;
0352     }
0353 
0354     if (QFile::exists(m_firstBackupSession)) {
0355         QFile::remove(m_secondBackupSession);
0356         QFile::copy(m_firstBackupSession, m_secondBackupSession);
0357     }
0358 
0359     QFile::remove(m_firstBackupSession);
0360     QFile::copy(m_lastActiveSessionPath, m_firstBackupSession);
0361 }
0362 
0363 void SessionManager::writeCurrentSession(const QString &filePath)
0364 {
0365     QSaveFile file(filePath);
0366     if (!file.open(QIODevice::WriteOnly) || file.write(mApp->saveState()) == -1) {
0367         qWarning() << "Error! can not write the current session file: " << filePath << file.errorString();
0368         return;
0369     }
0370     file.commit();
0371 }
0372 
0373 void SessionManager::openSessionManagerDialog()
0374 {
0375     auto *dialog = new SessionManagerDialog(mApp->getWindow());
0376     dialog->open();
0377 }
0378 
0379 void SessionManager::autoSaveLastSession()
0380 {
0381     if (mApp->isPrivate() || mApp->windowCount() == 0) {
0382         return;
0383     }
0384 
0385     saveSettings();
0386     writeCurrentSession(m_lastActiveSessionPath);
0387 }
0388 
0389 QString SessionManager::askSessionFromUser()
0390 {
0391     fillSessionsMetaDataListIfNeeded();
0392 
0393     QDialog dialog(mApp->getWindow(), Qt::WindowStaysOnTopHint);
0394     QLabel label(tr("Please select the startup session:"), &dialog);
0395     QComboBox comboBox(&dialog);
0396     QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
0397     connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
0398     connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
0399 
0400     QVBoxLayout layout;
0401     layout.addWidget(&label);
0402     layout.addWidget(&comboBox);
0403     layout.addWidget(&buttonBox);
0404     dialog.setLayout(&layout);
0405 
0406     const QFileInfo lastActiveSessionFileInfo(m_lastActiveSessionPath);
0407 
0408     for (const SessionMetaData &metaData : m_sessionsMetaDataList) {
0409         if (QFileInfo(metaData.filePath) != lastActiveSessionFileInfo) {
0410             comboBox.addItem(metaData.name, metaData.filePath);
0411         }
0412         else {
0413             comboBox.insertItem(0, tr("%1 (last session)").arg(metaData.name), metaData.filePath);
0414         }
0415     }
0416 
0417     comboBox.setCurrentIndex(0);
0418 
0419     if (dialog.exec() == QDialog::Accepted) {
0420         m_lastActiveSessionPath = comboBox.currentData().toString();
0421     }
0422 
0423     return m_lastActiveSessionPath;
0424 }