Skip to content

Commit 83b51a4

Browse files
committed
Support recovery files
1 parent 573abbe commit 83b51a4

11 files changed

Lines changed: 466 additions & 26 deletions

File tree

src/plugins/coreplugin/internal/BehaviorPreference.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ namespace Core::Internal {
2525
QString proxyUsername{};
2626
QString proxyPassword{};
2727
BehaviorPreference::FileOption fileOption{};
28+
bool documentLogEnabled{};
2829
bool useCustomFont{};
2930
QString fontFamily{};
3031
QString fontStyle{};
@@ -87,6 +88,8 @@ namespace Core::Internal {
8788
emit proxyPasswordChanged();
8889
d->fileOption = settings->value("fileOption", QVariant::fromValue(FO_LockOpenedFiles | FO_CheckForExternalChangedOnSave)).value<FileOption>();
8990
emit fileOptionChanged();
91+
d->documentLogEnabled = settings->value("documentLogEnabled", true).toBool();
92+
emit documentLogEnabledChanged();
9093
d->useCustomFont = settings->value("useCustomFont", false).toBool();
9194
emit useCustomFontChanged();
9295
d->fontFamily = settings->value("fontFamily", QApplication::font().family()).toString();
@@ -143,6 +146,7 @@ namespace Core::Internal {
143146
settings->setValue("proxyUsername", d->proxyUsername);
144147
settings->setValue("proxyPassword", d->proxyPassword);
145148
settings->setValue("fileOption", static_cast<int>(d->fileOption));
149+
settings->setValue("documentLogEnabled", d->documentLogEnabled);
146150
settings->setValue("useCustomFont", d->useCustomFont);
147151
settings->setValue("fontFamily", d->fontFamily);
148152
settings->setValue("fontStyle", d->fontStyle);
@@ -320,6 +324,17 @@ namespace Core::Internal {
320324
d->fileOption = fileOption;
321325
emit m_instance->fileOptionChanged();
322326
}
327+
bool BehaviorPreference::documentLogEnabled() {
328+
M_INSTANCE_D;
329+
return d->documentLogEnabled;
330+
}
331+
void BehaviorPreference::setDocumentLogEnabled(bool documentLogEnabled) {
332+
M_INSTANCE_D;
333+
if (d->documentLogEnabled == documentLogEnabled)
334+
return;
335+
d->documentLogEnabled = documentLogEnabled;
336+
emit m_instance->documentLogEnabledChanged();
337+
}
323338
bool BehaviorPreference::useCustomFont() {
324339
M_INSTANCE_D;
325340
return d->useCustomFont;

src/plugins/coreplugin/internal/BehaviorPreference.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace Core::Internal {
3333
Q_PROPERTY(QString proxyUsername READ proxyUsername WRITE setProxyUsername NOTIFY proxyUsernameChanged)
3434
Q_PROPERTY(QString proxyPassword READ proxyPassword WRITE setProxyPassword NOTIFY proxyPasswordChanged)
3535
Q_PROPERTY(BehaviorPreference::FileOption fileOption READ fileOption WRITE setFileOption NOTIFY fileOptionChanged)
36+
Q_PROPERTY(bool documentLogEnabled READ documentLogEnabled WRITE setDocumentLogEnabled NOTIFY documentLogEnabledChanged)
3637
Q_PROPERTY(bool useCustomFont READ useCustomFont WRITE setUseCustomFont NOTIFY useCustomFontChanged)
3738
Q_PROPERTY(QString fontFamily READ fontFamily WRITE setFontFamily NOTIFY fontFamilyChanged)
3839
Q_PROPERTY(QString fontStyle READ fontStyle WRITE setFontStyle NOTIFY fontStyleChanged)
@@ -130,6 +131,9 @@ namespace Core::Internal {
130131
static FileOption fileOption();
131132
static void setFileOption(FileOption fileOption);
132133

134+
static bool documentLogEnabled();
135+
static void setDocumentLogEnabled(bool documentLogEnabled);
136+
133137
static bool useCustomFont();
134138
static void setUseCustomFont(bool useCustomFont);
135139

@@ -217,6 +221,7 @@ namespace Core::Internal {
217221
void proxyUsernameChanged();
218222
void proxyPasswordChanged();
219223
void fileOptionChanged();
224+
void documentLogEnabledChanged();
220225
void useCustomFontChanged();
221226
void fontFamilyChanged();
222227
void fontStyleChanged();

src/plugins/coreplugin/internal/addon/CloseSaveCheckAddOn.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ namespace Core::Internal {
6767
}
6868
if (button == SVS::SVSCraft::No) {
6969
QDir runtimeDataDir(ApplicationInfo::applicationLocation(ApplicationInfo::RuntimeData));
70-
auto tempPath = runtimeDataDir.filePath(QStringLiteral("latest_unsaved_project.dspx"));
70+
auto tempPath = runtimeDataDir.filePath(QStringLiteral("recovery/latest_unsaved_project.dspx"));
7171
return projectContext->saveCopy(tempPath);
7272
}
7373

src/plugins/coreplugin/internal/addon/RecentFileAddOn.cpp

Lines changed: 204 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,44 @@
11
#include "RecentFileAddOn.h"
22

3+
#include <memory>
4+
35
#include <QDir>
6+
#include <QFile>
47
#include <QFileInfo>
8+
#include <QLockFile>
9+
#include <QLoggingCategory>
510
#include <QQmlComponent>
611
#include <QStandardItemModel>
712

13+
#include <CoreApi/applicationinfo.h>
814
#include <CoreApi/recentfilecollection.h>
915
#include <CoreApi/runtimeinterface.h>
1016

17+
#include <dspxmodelCore/Document.h>
18+
#include <dspxmodelORM/Model.h>
19+
#include <opendspx/model.h>
20+
1121
#include <QAKQuick/quickactioncontext.h>
1222

1323
#include <uishell/USDef.h>
1424

1525
#include <coreplugin/CoreInterface.h>
1626
#include <coreplugin/HomeWindowInterface.h>
27+
#include <coreplugin/ProjectDocumentContext.h>
1728
#include <coreplugin/ProjectWindowInterface.h>
1829

1930
namespace Core::Internal {
2031

32+
Q_STATIC_LOGGING_CATEGORY(lcRecentFileAddOn, "diffscope.core.recentfileaddon")
33+
34+
enum RecoveryFileKind {
35+
RFK_LatestUnsavedProject,
36+
RFK_DocumentLogDirectory,
37+
};
38+
39+
constexpr int RecoveryFileKindRole = Qt::UserRole + 100;
40+
constexpr int RecoveryFileStoragePathRole = Qt::UserRole + 101;
41+
2142
class RecentFilesModel : public QStandardItemModel {
2243
public:
2344
using QStandardItemModel::QStandardItemModel;
@@ -26,24 +47,19 @@ namespace Core::Internal {
2647
static const QHash<int, QByteArray> m{
2748
{UIShell::USDef::RF_NameRole, "name"},
2849
{UIShell::USDef::RF_PathRole, "path"},
50+
{UIShell::USDef::RF_LastModifiedTextRole, "lastModifiedText"},
51+
{UIShell::USDef::RF_ThumbnailRole, "thumbnail"},
52+
{UIShell::USDef::RF_IconRole, "icon"},
53+
{UIShell::USDef::RF_ColorizeRole, "colorize"},
2954
};
3055
return m;
3156
}
3257
};
3358

34-
RecentFileAddOn::RecentFileAddOn(QObject *parent) : WindowInterfaceAddOn(parent), m_recentFilesModel(new RecentFilesModel(this)), m_recoveryFilesModel(new QStandardItemModel(this)) {
59+
RecentFileAddOn::RecentFileAddOn(QObject *parent) : WindowInterfaceAddOn(parent), m_recentFilesModel(new RecentFilesModel(this)), m_recoveryFilesModel(new RecentFilesModel(this)) {
3560
connect(CoreInterface::recentFileCollection(), &RecentFileCollection::recentFilesChanged, this, &RecentFileAddOn::updateRecentFilesModel);
3661
updateRecentFilesModel();
37-
// TODO mock data
38-
{
39-
auto item = new QStandardItem;
40-
item->setData("mock_recovery_file.dspx", UIShell::USDef::RF_NameRole);
41-
item->setData("/path/to/mock_recovery_file.dspx", UIShell::USDef::RF_PathRole);
42-
item->setData(QLocale().toString(QDateTime::fromString("1919-08-10T11:45:14", Qt::ISODate), QLocale::ShortFormat), UIShell::USDef::RF_LastModifiedTextRole);
43-
item->setData(QUrl(), UIShell::USDef::RF_ThumbnailRole);
44-
item->setData(QUrl("image://appicon/dspx"), UIShell::USDef::RF_IconRole);
45-
m_recoveryFilesModel->appendRow(item);
46-
}
62+
updateRecoveryFilesModel();
4763
}
4864

4965
RecentFileAddOn::~RecentFileAddOn() = default;
@@ -105,6 +121,10 @@ namespace Core::Internal {
105121
return m_recoveryFilesModel;
106122
}
107123

124+
int RecentFileAddOn::recoveryFileCount() const {
125+
return m_recoveryFileCount;
126+
}
127+
108128
bool RecentFileAddOn::isHomeWindow() const {
109129
return static_cast<bool>(qobject_cast<HomeWindowInterface *>(windowHandle()));
110130
}
@@ -126,6 +146,99 @@ namespace Core::Internal {
126146
}
127147
}
128148

149+
static QString recoveryRootPath() {
150+
return QDir(ApplicationInfo::applicationLocation(ApplicationInfo::RuntimeData)).filePath(QStringLiteral("recovery"));
151+
}
152+
153+
static QString latestUnsavedProjectPath() {
154+
return QDir(recoveryRootPath()).filePath(QStringLiteral("latest_unsaved_project.dspx"));
155+
}
156+
157+
static bool recoveryDirectoryInUse(const QDir &directory) {
158+
QLockFile lock(directory.filePath(QStringLiteral("active.lock")));
159+
if (lock.tryLock(0)) {
160+
lock.unlock();
161+
return false;
162+
}
163+
return true;
164+
}
165+
166+
static QString formatLastModified(const QFileInfo &fileInfo) {
167+
if (!fileInfo.exists()) {
168+
return {};
169+
}
170+
return QLocale().toString(fileInfo.lastModified(), QLocale::ShortFormat);
171+
}
172+
173+
static QString recoveryDirectoryDisplayName(const QDir &directory) {
174+
QFile nameFile(directory.filePath(QStringLiteral("name")));
175+
if (nameFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
176+
auto name = QString::fromUtf8(nameFile.readAll()).trimmed();
177+
if (!name.isEmpty()) {
178+
return name;
179+
}
180+
}
181+
return directory.dirName();
182+
}
183+
184+
static QString defaultDocumentNameFromRecoveryName(const QString &name) {
185+
if (QFileInfo(name).suffix().compare(QStringLiteral("dspx"), Qt::CaseInsensitive) == 0) {
186+
return name;
187+
}
188+
return name + QStringLiteral(".dspx");
189+
}
190+
191+
void RecentFileAddOn::updateRecoveryFilesModel() {
192+
static const QUrl dspxIconUrl{"image://appicon/dspx"};
193+
m_recoveryFilesModel->clear();
194+
int recoveryFileCount = 0;
195+
196+
const auto latestPath = latestUnsavedProjectPath();
197+
QFileInfo latestFileInfo(latestPath);
198+
if (latestFileInfo.exists() && latestFileInfo.isFile()) {
199+
auto item = new QStandardItem;
200+
item->setData(tr("Unsaved project from last close"), UIShell::USDef::RF_NameRole);
201+
item->setData(QString(), UIShell::USDef::RF_PathRole);
202+
item->setData(formatLastModified(latestFileInfo), UIShell::USDef::RF_LastModifiedTextRole);
203+
item->setData(QUrl(), UIShell::USDef::RF_ThumbnailRole);
204+
item->setData(dspxIconUrl, UIShell::USDef::RF_IconRole);
205+
item->setData(false, UIShell::USDef::RF_ColorizeRole);
206+
item->setData(RFK_LatestUnsavedProject, RecoveryFileKindRole);
207+
item->setData(QDir::toNativeSeparators(latestFileInfo.absoluteFilePath()), RecoveryFileStoragePathRole);
208+
m_recoveryFilesModel->appendRow(item);
209+
}
210+
211+
QDir recoveryRoot(recoveryRootPath());
212+
for (const auto &directoryInfo : recoveryRoot.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time)) {
213+
QDir directory(directoryInfo.absoluteFilePath());
214+
if (recoveryDirectoryInUse(directory)) {
215+
continue;
216+
}
217+
const QFileInfo logsFileInfo(directory.filePath(QStringLiteral("logs")));
218+
const QFileInfo snapshotFileInfo(directory.filePath(QStringLiteral("snapshot")));
219+
const auto modifiedText = logsFileInfo.exists()
220+
? formatLastModified(logsFileInfo)
221+
: formatLastModified(snapshotFileInfo);
222+
223+
auto item = new QStandardItem;
224+
item->setData(recoveryDirectoryDisplayName(directory), UIShell::USDef::RF_NameRole);
225+
item->setData(QString(), UIShell::USDef::RF_PathRole);
226+
item->setData(modifiedText, UIShell::USDef::RF_LastModifiedTextRole);
227+
item->setData(QUrl(), UIShell::USDef::RF_ThumbnailRole);
228+
item->setData(dspxIconUrl, UIShell::USDef::RF_IconRole);
229+
item->setData(false, UIShell::USDef::RF_ColorizeRole);
230+
item->setData(RFK_DocumentLogDirectory, RecoveryFileKindRole);
231+
item->setData(QDir::toNativeSeparators(directoryInfo.absoluteFilePath()), RecoveryFileStoragePathRole);
232+
m_recoveryFilesModel->appendRow(item);
233+
++recoveryFileCount;
234+
}
235+
236+
if (m_recoveryFileCount != recoveryFileCount) {
237+
m_recoveryFileCount = recoveryFileCount;
238+
Q_EMIT recoveryFileCountChanged();
239+
}
240+
}
241+
129242
void RecentFileAddOn::openRecentFile(int index) {
130243
auto filePath = CoreInterface::recentFileCollection()->recentFiles().at(index);
131244
CoreInterface::openFile(filePath);
@@ -134,4 +247,84 @@ namespace Core::Internal {
134247
void RecentFileAddOn::removeRecentFile(int index) {
135248
CoreInterface::recentFileCollection()->removeRecentFile(CoreInterface::recentFileCollection()->recentFiles().at(index));
136249
}
250+
251+
void RecentFileAddOn::openRecoveryFile(int index) {
252+
if (index < 0 || index >= m_recoveryFilesModel->rowCount()) {
253+
return;
254+
}
255+
auto item = m_recoveryFilesModel->item(index);
256+
const auto path = item->data(RecoveryFileStoragePathRole).toString();
257+
const auto kind = item->data(RecoveryFileKindRole).toInt();
258+
259+
if (kind == RFK_LatestUnsavedProject) {
260+
CoreInterface::newFileFromTemplate(path);
261+
return;
262+
}
263+
264+
QDir recoveryDirectory(path);
265+
QFile snapshotFile(recoveryDirectory.filePath(QStringLiteral("snapshot")));
266+
if (!snapshotFile.open(QIODevice::ReadOnly)) {
267+
qCWarning(lcRecentFileAddOn) << "Failed to open recovery snapshot:" << snapshotFile.fileName() << snapshotFile.errorString();
268+
return;
269+
}
270+
271+
QFile logsFile(recoveryDirectory.filePath(QStringLiteral("logs")));
272+
QIODevice *logsDevice = nullptr;
273+
if (logsFile.exists()) {
274+
if (!logsFile.open(QIODevice::ReadOnly)) {
275+
qCWarning(lcRecentFileAddOn) << "Failed to open recovery log:" << logsFile.fileName() << logsFile.errorString();
276+
return;
277+
}
278+
logsDevice = &logsFile;
279+
}
280+
281+
std::unique_ptr<dspx::Document> restoredDocument;
282+
try {
283+
restoredDocument.reset(dspx::Document::restore(&snapshotFile, logsDevice));
284+
} catch (...) {
285+
qCWarning(lcRecentFileAddOn) << "Failed to restore document log:" << recoveryDirectory.absolutePath();
286+
return;
287+
}
288+
289+
dspx::Model restoredModel(restoredDocument.get());
290+
auto openDspxModel = restoredModel.toOpenDSPX();
291+
auto defaultDocumentName = defaultDocumentNameFromRecoveryName(item->data(UIShell::USDef::RF_NameRole).toString());
292+
auto projectDocumentContext = std::make_unique<ProjectDocumentContext>();
293+
if (!projectDocumentContext->newFile(openDspxModel, defaultDocumentName, false)) {
294+
qCWarning(lcRecentFileAddOn) << "Failed to create project context from recovery directory:" << recoveryDirectory.absolutePath();
295+
return;
296+
}
297+
CoreInterface::createProjectWindow(projectDocumentContext.release());
298+
}
299+
300+
void RecentFileAddOn::removeRecoveryFile(int index) {
301+
if (index < 0 || index >= m_recoveryFilesModel->rowCount()) {
302+
return;
303+
}
304+
auto item = m_recoveryFilesModel->item(index);
305+
const auto path = item->data(RecoveryFileStoragePathRole).toString();
306+
const auto kind = item->data(RecoveryFileKindRole).toInt();
307+
if (kind == RFK_LatestUnsavedProject) {
308+
QFile::remove(path);
309+
} else {
310+
QDir(path).removeRecursively();
311+
}
312+
updateRecoveryFilesModel();
313+
}
314+
315+
void RecentFileAddOn::clearRecoveryFiles() {
316+
auto root = QDir(recoveryRootPath());
317+
QFile::remove(latestUnsavedProjectPath());
318+
for (const auto &directoryInfo : root.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
319+
QDir directory(directoryInfo.absoluteFilePath());
320+
if (!recoveryDirectoryInUse(directory)) {
321+
directory.removeRecursively();
322+
}
323+
}
324+
updateRecoveryFilesModel();
325+
}
326+
327+
void RecentFileAddOn::refreshRecoveryFiles() {
328+
updateRecoveryFilesModel();
329+
}
137330
}

src/plugins/coreplugin/internal/addon/RecentFileAddOn.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Core::Internal {
1616
QML_UNCREATABLE("")
1717
Q_PROPERTY(QAbstractItemModel *recentFilesModel READ recentFilesModel CONSTANT)
1818
Q_PROPERTY(QAbstractItemModel *recoveryFilesModel READ recoveryFilesModel CONSTANT)
19+
Q_PROPERTY(int recoveryFileCount READ recoveryFileCount NOTIFY recoveryFileCountChanged)
1920
Q_PROPERTY(bool isHomeWindow READ isHomeWindow CONSTANT)
2021
public:
2122
explicit RecentFileAddOn(QObject *parent = nullptr);
@@ -27,18 +28,28 @@ namespace Core::Internal {
2728

2829
QAbstractItemModel *recentFilesModel() const;
2930
QAbstractItemModel *recoveryFilesModel() const;
31+
int recoveryFileCount() const;
3032

3133
bool isHomeWindow() const;
3234

3335
public Q_SLOTS:
3436
static void openRecentFile(int index);
3537
static void removeRecentFile(int index);
38+
Q_INVOKABLE void openRecoveryFile(int index);
39+
Q_INVOKABLE void removeRecoveryFile(int index);
40+
Q_INVOKABLE void clearRecoveryFiles();
41+
Q_INVOKABLE void refreshRecoveryFiles();
42+
43+
Q_SIGNALS:
44+
void recoveryFileCountChanged();
3645

3746
private:
3847
QStandardItemModel *m_recentFilesModel;
3948
QStandardItemModel *m_recoveryFilesModel;
49+
int m_recoveryFileCount{};
4050

4151
void updateRecentFilesModel() const;
52+
void updateRecoveryFilesModel();
4253

4354
};
4455

0 commit comments

Comments
 (0)