Falkon Develop
Cross-platform Qt-based web browser
sessionmanager.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2017 Razi Alavizadeh <s.r.alavizadeh@gmail.com>
4* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
5*
6* This program is free software: you can redistribute it and/or modify
7* it under the terms of the GNU General Public License as published by
8* the Free Software Foundation, either version 3 of the License, or
9* (at your option) any later version.
10*
11* This program is distributed in the hope that it will be useful,
12* but WITHOUT ANY WARRANTY; without even the implied warranty of
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14* GNU General Public License for more details.
15*
16* You should have received a copy of the GNU General Public License
17* along with this program. If not, see <http://www.gnu.org/licenses/>.
18* ============================================================ */
19#include "browserwindow.h"
20#include "datapaths.h"
21#include "mainapplication.h"
22#include "restoremanager.h"
23#include "sessionmanager.h"
25#include "settings.h"
26
27#include <QAction>
28#include <QActionGroup>
29#include <QComboBox>
30#include <QDateTime>
31#include <QDialogButtonBox>
32#include <QDir>
33#include <QFileSystemWatcher>
34#include <QInputDialog>
35#include <QLabel>
36#include <QMenu>
37#include <QMessageBox>
38#include <QVBoxLayout>
39#include <QSaveFile>
40
42 : QObject(parent)
43 , m_firstBackupSession(DataPaths::currentProfilePath() + QL1S("/session.dat.old"))
44 , m_secondBackupSession(DataPaths::currentProfilePath() + QL1S("/session.dat.old1"))
45{
46 auto* sessionFilesWatcher = new QFileSystemWatcher({DataPaths::path(DataPaths::Sessions)}, this);
47 connect(sessionFilesWatcher, &QFileSystemWatcher::directoryChanged, this, &SessionManager::sessionsDirectoryChanged);
48 connect(sessionFilesWatcher, &QFileSystemWatcher::directoryChanged, this, &SessionManager::sessionsMetaDataChanged);
49
51}
52
53void SessionManager::aboutToShowSessionsMenu()
54{
55 auto* menu = qobject_cast<QMenu*>(sender());
56 menu->clear();
57
58 auto *group = new QActionGroup(menu);
59
60 const auto sessions = sessionMetaData(/*withBackups*/ false);
61 for (const SessionManager::SessionMetaData &metaData : sessions) {
62 QAction* action = menu->addAction(metaData.name);
63 action->setCheckable(true);
64 action->setChecked(metaData.isActive);
65 group->addAction(action);
66 connect(action, &QAction::triggered, this, [=]() {
67 switchToSession(metaData.filePath);
68 });
69 }
70}
71
72void SessionManager::sessionsDirectoryChanged()
73{
74 m_sessionsMetaDataList.clear();
75}
76
77void SessionManager::openSession(QString sessionFilePath, SessionManager::SessionFlags flags)
78{
79 if (sessionFilePath.isEmpty()) {
80 auto* action = qobject_cast<QAction*>(sender());
81 if (!action)
82 return;
83
84 sessionFilePath = action->data().toString();
85 }
86
87 if (isActive(sessionFilePath)) {
88 return;
89 }
90
91 RestoreData sessionData;
92 RestoreManager::createFromFile(sessionFilePath, sessionData);
93
94 if (!sessionData.isValid())
95 return;
96
97 BrowserWindow* window = mApp->getWindow();
98 if (flags.testFlag(SwitchSession)) {
99 writeCurrentSession(m_lastActiveSessionPath);
100
101 window = mApp->createWindow(Qz::BW_OtherRestoredWindow);
102 for (BrowserWindow* win : mApp->windows()) {
103 if (win != window)
104 win->close();
105 }
106
107 if (!flags.testFlag(ReplaceSession)) {
108 m_lastActiveSessionPath = QFileInfo(sessionFilePath).canonicalFilePath();
109 m_sessionsMetaDataList.clear();
110 }
111 }
112
113 mApp->openSession(window, sessionData);
114}
115
116void SessionManager::renameSession(QString sessionFilePath, SessionManager::SessionFlags flags)
117{
118 if (sessionFilePath.isEmpty()) {
119 auto* action = qobject_cast<QAction*>(sender());
120 if (!action)
121 return;
122
123 sessionFilePath = action->data().toString();
124 }
125
126 bool ok;
127 const QString suggestedName = QFileInfo(sessionFilePath).completeBaseName() + (flags.testFlag(CloneSession) ? tr("_cloned") : tr("_renamed"));
128 QString newName = QInputDialog::getText(mApp->activeWindow(), (flags.testFlag(CloneSession) ? tr("Clone Session") : tr("Rename Session")),
129 tr("Please enter a new name:"), QLineEdit::Normal,
130 suggestedName, &ok);
131
132 if (!ok)
133 return;
134
135 const QString newSessionPath = QSL("%1/%2.dat").arg(DataPaths::path(DataPaths::Sessions), newName);
136 if (QFile::exists(newSessionPath)) {
137 QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("The session file \"%1\" exists. Please enter another name.").arg(newName));
138 renameSession(sessionFilePath, flags);
139 return;
140 }
141
142 if (flags.testFlag(CloneSession)) {
143 if (!QFile::copy(sessionFilePath, newSessionPath)) {
144 QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("An error occurred when cloning session file."));
145 return;
146 }
147 } else {
148 if (!QFile::rename(sessionFilePath, newSessionPath)) {
149 QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("An error occurred when renaming session file."));
150 return;
151 }
152 if (isActive(sessionFilePath)) {
153 m_lastActiveSessionPath = newSessionPath;
154 m_sessionsMetaDataList.clear();
155 }
156 }
157}
158
159void SessionManager::saveSession()
160{
161 bool ok;
162 QString sessionName = QInputDialog::getText(mApp->activeWindow(), tr("Save Session"),
163 tr("Please enter a name to save session:"), QLineEdit::Normal,
164 tr("Saved Session (%1)").arg(QDateTime::currentDateTime().toString(QSL("dd MMM yyyy HH-mm-ss"))), &ok);
165
166 if (!ok)
167 return;
168
169 const QString filePath = QSL("%1/%2.dat").arg(DataPaths::path(DataPaths::Sessions), sessionName);
170 if (QFile::exists(filePath)) {
171 QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("The session file \"%1\" exists. Please enter another name.").arg(sessionName));
172 saveSession();
173 return;
174 }
175
176 writeCurrentSession(filePath);
177}
178
179void SessionManager::replaceSession(const QString &filePath)
180{
181 QMessageBox::StandardButton result = QMessageBox::information(mApp->activeWindow(), tr("Restore Backup"), tr("Are you sure you want to replace current session?"),
182 QMessageBox::Yes | QMessageBox::No);
183 if (result == QMessageBox::Yes) {
184 openSession(filePath, ReplaceSession);
185 }
186}
187
188void SessionManager::switchToSession(const QString &filePath)
189{
190 openSession(filePath, SwitchSession);
191}
192
193void SessionManager::cloneSession(const QString &filePath)
194{
195 renameSession(filePath, CloneSession);
196}
197
198void SessionManager::deleteSession(const QString &filePath)
199{
200 QMessageBox::StandardButton result = QMessageBox::information(mApp->activeWindow(), tr("Delete Session"), tr("Are you sure you want to delete session '%1'?")
201 .arg(QFileInfo(filePath).completeBaseName()), QMessageBox::Yes | QMessageBox::No);
202 if (result == QMessageBox::Yes) {
203 QFile::remove(filePath);
204 }
205}
206
207void SessionManager::newSession()
208{
209 bool ok;
210 QString sessionName = QInputDialog::getText(mApp->activeWindow(), tr("New Session"),
211 tr("Please enter a name to create new session:"), QLineEdit::Normal,
212 tr("New Session (%1)").arg(QDateTime::currentDateTime().toString(QSL("dd MMM yyyy HH-mm-ss"))), &ok);
213
214 if (!ok)
215 return;
216
217 const QString filePath = QStringLiteral("%1/%2.dat").arg(DataPaths::path(DataPaths::Sessions), sessionName);
218 if (QFile::exists(filePath)) {
219 QMessageBox::information(mApp->activeWindow(), tr("Error!"), tr("The session file \"%1\" exists. Please enter another name.").arg(sessionName));
220 newSession();
221 return;
222 }
223
224 writeCurrentSession(m_lastActiveSessionPath);
225
226 BrowserWindow* window = mApp->createWindow(Qz::BW_NewWindow);
227 for (BrowserWindow* win : mApp->windows()) {
228 if (win != window)
229 win->close();
230 }
231
232 m_lastActiveSessionPath = filePath;
234}
235
236QList<SessionManager::SessionMetaData> SessionManager::sessionMetaData(bool withBackups)
237{
238 fillSessionsMetaDataListIfNeeded();
239
240 auto out = m_sessionsMetaDataList;
241
242 if (withBackups && QFile::exists(m_firstBackupSession)) {
243 SessionMetaData data;
244 data.name = tr("Backup 1");
245 data.filePath = m_firstBackupSession;
246 data.isBackup = true;
247 out.append(data);
248 }
249 if (withBackups && QFile::exists(m_secondBackupSession)) {
250 SessionMetaData data;
251 data.name = tr("Backup 2");
252 data.filePath = m_secondBackupSession;
253 data.isBackup = true;
254 out.append(data);
255 }
256
257 return out;
258}
259
260bool SessionManager::isActive(const QString &filePath) const
261{
262 return QFileInfo(filePath) == QFileInfo(m_lastActiveSessionPath);
263}
264
265bool SessionManager::isActive(const QFileInfo &fileInfo) const
266{
267 return fileInfo == QFileInfo(m_lastActiveSessionPath);
268}
269
270void SessionManager::fillSessionsMetaDataListIfNeeded()
271{
272 if (!m_sessionsMetaDataList.isEmpty())
273 return;
274
276
277 const QFileInfoList sessionFiles = QFileInfoList() << QFileInfo(defaultSessionPath()) << dir.entryInfoList({QSL("*.*")}, QDir::Files, QDir::Time);
278
279 QStringList fileNames;
280
281 for (int i = 0; i < sessionFiles.size(); ++i) {
282 const QFileInfo &fileInfo = sessionFiles.at(i);
283
284 if (!RestoreManager::validateFile(fileInfo.absoluteFilePath()))
285 continue;
286
287 SessionMetaData metaData;
288 metaData.name = fileInfo.completeBaseName();
289
290 if (fileInfo == QFileInfo(defaultSessionPath())) {
291 metaData.name = tr("Default Session");
292 metaData.isDefault = true;
293 } else if (fileNames.contains(fileInfo.completeBaseName())) {
294 metaData.name = fileInfo.fileName();
295 } else {
296 metaData.name = fileInfo.completeBaseName();
297 }
298
299 if (isActive(fileInfo)) {
300 metaData.isActive = true;
301 }
302
303 fileNames << metaData.name;
304 metaData.filePath = fileInfo.canonicalFilePath();
305
306 m_sessionsMetaDataList << metaData;
307 }
308}
309
311{
312 QDir sessionsDir(DataPaths::path(DataPaths::Sessions));
313
314 Settings settings;
315 settings.beginGroup(QSL("Web-Browser-Settings"));
316 m_lastActiveSessionPath = settings.value(QSL("lastActiveSessionPath"), defaultSessionPath()).toString();
317 settings.endGroup();
318
319 if (QDir::isRelativePath(m_lastActiveSessionPath)) {
320 m_lastActiveSessionPath = sessionsDir.absoluteFilePath(m_lastActiveSessionPath);
321 }
322
323 // Fallback to default session
324 if (!RestoreManager::validateFile(m_lastActiveSessionPath))
325 m_lastActiveSessionPath = defaultSessionPath();
326}
327
329{
330 QDir sessionsDir(DataPaths::path(DataPaths::Sessions));
331
332 Settings settings;
333 settings.beginGroup(QSL("Web-Browser-Settings"));
334 settings.setValue(QSL("lastActiveSessionPath"), sessionsDir.relativeFilePath(m_lastActiveSessionPath));
335 settings.endGroup();
336}
337
339{
340 return DataPaths::currentProfilePath() + QL1S("/session.dat");
341}
342
344{
345 return m_lastActiveSessionPath;
346}
347
349{
350 if (!QFile::exists(m_lastActiveSessionPath)) {
351 return;
352 }
353
354 if (QFile::exists(m_firstBackupSession)) {
355 QFile::remove(m_secondBackupSession);
356 QFile::copy(m_firstBackupSession, m_secondBackupSession);
357 }
358
359 QFile::remove(m_firstBackupSession);
360 QFile::copy(m_lastActiveSessionPath, m_firstBackupSession);
361}
362
363void SessionManager::writeCurrentSession(const QString &filePath)
364{
365 QSaveFile file(filePath);
366 if (!file.open(QIODevice::WriteOnly) || file.write(mApp->saveState()) == -1) {
367 qWarning() << "Error! can not write the current session file: " << filePath << file.errorString();
368 return;
369 }
370 file.commit();
371}
372
374{
375 auto *dialog = new SessionManagerDialog(mApp->getWindow());
376 dialog->open();
377}
378
380{
381 if (mApp->isPrivate() || mApp->windowCount() == 0) {
382 return;
383 }
384
385 saveSettings();
386 writeCurrentSession(m_lastActiveSessionPath);
387}
388
390{
391 fillSessionsMetaDataListIfNeeded();
392
393 QDialog dialog(mApp->getWindow(), Qt::WindowStaysOnTopHint);
394 QLabel label(tr("Please select the startup session:"), &dialog);
395 QComboBox comboBox(&dialog);
396 QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
397 connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
398 connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
399
400 QVBoxLayout layout;
401 layout.addWidget(&label);
402 layout.addWidget(&comboBox);
403 layout.addWidget(&buttonBox);
404 dialog.setLayout(&layout);
405
406 const QFileInfo lastActiveSessionFileInfo(m_lastActiveSessionPath);
407
408 for (const SessionMetaData &metaData : m_sessionsMetaDataList) {
409 if (QFileInfo(metaData.filePath) != lastActiveSessionFileInfo) {
410 comboBox.addItem(metaData.name, metaData.filePath);
411 }
412 else {
413 comboBox.insertItem(0, tr("%1 (last session)").arg(metaData.name), metaData.filePath);
414 }
415 }
416
417 comboBox.setCurrentIndex(0);
418
419 if (dialog.exec() == QDialog::Accepted) {
420 m_lastActiveSessionPath = comboBox.currentData().toString();
421 }
422
423 return m_lastActiveSessionPath;
424}
@ Sessions
Definition: datapaths.h:39
static QString path(Path type)
Definition: datapaths.cpp:66
static QString currentProfilePath()
Definition: datapaths.cpp:95
static bool validateFile(const QString &file)
static void createFromFile(const QString &file, RestoreData &data)
friend class SessionManagerDialog
static QString defaultSessionPath()
void backupSavedSessions()
void sessionsMetaDataChanged()
void openSessionManagerDialog()
QString askSessionFromUser()
void autoSaveLastSession()
QString lastActiveSessionPath() const
void writeCurrentSession(const QString &filePath)
SessionManager(QObject *parent=nullptr)
void beginGroup(const QString &prefix)
Definition: settings.cpp:79
void endGroup()
Definition: settings.cpp:84
QVariant value(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:74
void setValue(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:69
#define mApp
@ BW_NewWindow
Definition: qzcommon.h:67
@ BW_OtherRestoredWindow
Definition: qzcommon.h:66
i
Definition: i18n.py:23
#define QL1S(x)
Definition: qzcommon.h:44
#define QSL(x)
Definition: qzcommon.h:40
bool isValid() const