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
1930namespace 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}
0 commit comments