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 }