1 /* This file is part of the KDE project
2    SPDX-FileCopyrightText: 2021 Méven Car <meven.car@kdemail.net>
3 
4    SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "katestashmanager.h"
8 
9 #include "katedebug.h"
10 
11 #include "kateapp.h"
12 #include "katedocmanager.h"
13 #include "kateviewmanager.h"
14 
15 #include "ksharedconfig.h"
16 
17 #include <QDir>
18 #include <QFile>
19 #include <QSaveFile>
20 #include <QTextCodec>
21 #include <QUrl>
22 
KateStashManager(QObject * parent)23 KateStashManager::KateStashManager(QObject *parent)
24     : QObject(parent)
25 {
26 }
27 
clearStashForSession(const KateSession::Ptr session)28 void KateStashManager::clearStashForSession(const KateSession::Ptr session)
29 {
30     const QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
31     QDir dir(appDataPath);
32     if (dir.exists(QStringLiteral("stash"))) {
33         dir.cd(QStringLiteral("stash"));
34         const QString sessionName = session->name();
35         if (dir.exists(sessionName)) {
36             dir.cd(sessionName);
37             dir.removeRecursively();
38         }
39     }
40 }
41 
stashDocuments(KConfig * config,const QList<KTextEditor::Document * > & documents)42 void KateStashManager::stashDocuments(KConfig *config, const QList<KTextEditor::Document *> &documents)
43 {
44     // prepare stash directory
45     const QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
46     QDir dir(appDataPath);
47     dir.mkdir(QStringLiteral("stash"));
48     dir.cd(QStringLiteral("stash"));
49 
50     const auto activeSession = KateApp::self()->sessionManager()->activeSession();
51     if (!activeSession || activeSession->isAnonymous() || activeSession->name().isEmpty()) {
52         qDebug(LOG_KATE) << "Could not stash files without a session";
53         return;
54     }
55 
56     const QString sessionName = activeSession->name();
57     dir.mkdir(sessionName);
58     dir.cd(sessionName);
59 
60     int i = 0;
61     for (KTextEditor::Document *doc : documents) {
62         const QString entryName = QStringLiteral("Document %1").arg(i);
63         KConfigGroup cg(config, entryName);
64 
65         // stash the file content
66         if (doc->isModified()) {
67             stashDocument(doc, entryName, cg, dir.path());
68         }
69 
70         i++;
71     }
72 }
73 
willStashDoc(KTextEditor::Document * doc)74 bool KateStashManager::willStashDoc(KTextEditor::Document *doc)
75 {
76     const auto activeSession = KateApp::self()->sessionManager()->activeSession();
77     if (!activeSession || activeSession->isAnonymous() || activeSession->name().isEmpty()) {
78         return false;
79     }
80     if (doc->text().isEmpty()) {
81         return false;
82     }
83     if (doc->url().isEmpty()) {
84         return m_stashNewUnsavedFiles;
85     }
86     if (doc->url().isLocalFile()) {
87         const QString path = doc->url().toLocalFile();
88         if (path.startsWith(QDir::tempPath())) {
89             return false; // inside tmp resource, do not stash
90         }
91         return m_stashUnsavedChanges;
92     }
93     return false;
94 }
95 
stashDocument(KTextEditor::Document * doc,const QString & stashfileName,KConfigGroup & kconfig,const QString & path)96 void KateStashManager::stashDocument(KTextEditor::Document *doc, const QString &stashfileName, KConfigGroup &kconfig, const QString &path)
97 {
98     if (!willStashDoc(doc)) {
99         return;
100     }
101     // Stash changes
102     QString stashedFile = path + QStringLiteral("/") + stashfileName;
103 
104     // save the current document changes to stash
105     if (!doc->saveAs(QUrl::fromLocalFile(stashedFile))) {
106         qCWarning(LOG_KATE) << "Could not write to stash file" << stashedFile;
107         return;
108     }
109 
110     // write stash metadata to config
111     kconfig.writeEntry("stashedFile", stashedFile);
112     if (doc->url().isValid()) {
113         // save checksum for already-saved documents
114         kconfig.writeEntry("checksum", doc->checksum());
115     }
116 
117     kconfig.sync();
118 }
119 
popDocument(KTextEditor::Document * doc,const KConfigGroup & kconfig)120 bool KateStashManager::popDocument(KTextEditor::Document *doc, const KConfigGroup &kconfig)
121 {
122     if (!(kconfig.hasKey("stashedFile"))) {
123         return false;
124     }
125     qCDebug(LOG_KATE) << "popping stashed document" << doc->url();
126 
127     // read metadata
128     auto stashedFile = kconfig.readEntry("stashedFile");
129     auto url = QUrl(kconfig.readEntry("URL"));
130 
131     bool checksumOk = true;
132     if (url.isValid()) {
133         const auto sum = kconfig.readEntry(QStringLiteral("checksum")).toLatin1().constData();
134         checksumOk = sum != doc->checksum();
135     }
136 
137     if (checksumOk) {
138         // open file with stashed content
139         QFile file(stashedFile);
140         file.open(QIODevice::ReadOnly);
141         QTextStream out(&file);
142         const auto codec = QTextCodec::codecForName(kconfig.readEntry("Encoding").toLocal8Bit());
143         if (codec != nullptr) {
144             out.setCodec(codec);
145         }
146 
147         doc->setText(out.readAll());
148 
149         // clean stashed file
150         if (!file.remove()) {
151             qCWarning(LOG_KATE) << "Could not remove stash file" << stashedFile;
152         }
153 
154         return true;
155     } else {
156         return false;
157     }
158 }
159