1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
4 SPDX-FileCopyrightText: 1999-2005 David Faure <faure@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "readwritepart.h"
10#include "readwritepart_p.h"
11
12#include "kparts_logging.h"
13
14#define HAVE_KDIRNOTIFY __has_include(<KDirNotify>)
15#if HAVE_KDIRNOTIFY
16#include <KDirNotify>
17#endif
18
19#include <KIO/FileCopyJob>
20#include <KJobWidgets>
21#include <KLocalizedString>
22#include <KMessageBox>
23
24#include <QApplication>
25#include <QFileDialog>
26#include <QTemporaryFile>
27
28#include <qplatformdefs.h>
29
30#ifdef Q_OS_WIN
31#include <qt_windows.h> //CreateHardLink()
32#endif
33
34using namespace KParts;
35
36ReadWritePart::ReadWritePart(QObject *parent, const KPluginMetaData &data)
37 : ReadOnlyPart(*new ReadWritePartPrivate(this, data), parent)
38{
39}
40
41ReadWritePart::~ReadWritePart()
42{
43 // parent destructor will delete temp file
44 // we can't call our own closeUrl() here, because
45 // "cancel" wouldn't cancel anything. We have to assume
46 // the app called closeUrl() before destroying us.
47}
48
49void ReadWritePart::setReadWrite(bool readwrite)
50{
51 Q_D(ReadWritePart);
52
53 // Perhaps we should check isModified here and issue a warning if true
54 d->m_bReadWrite = readwrite;
55}
56
57void ReadWritePart::setModified(bool modified)
58{
59 Q_D(ReadWritePart);
60
61 // qDebug() << "setModified(" << (modified ? "true" : "false") << ")";
62 if (!d->m_bReadWrite && modified) {
63 qCCritical(KPARTSLOG) << "Can't set a read-only document to 'modified' !";
64 return;
65 }
66 d->m_bModified = modified;
67}
68
69void ReadWritePart::setModified()
70{
71 setModified(true);
72}
73
74bool ReadWritePart::queryClose()
75{
76 Q_D(ReadWritePart);
77
78 if (!isReadWrite() || !isModified()) {
79 return true;
80 }
81
82 QString docName = url().fileName();
83 if (docName.isEmpty()) {
84 docName = i18n("Untitled");
85 }
86
87 QWidget *parentWidget = widget();
88 if (!parentWidget) {
89 parentWidget = QApplication::activeWindow();
90 }
91
92 int res = KMessageBox::warningTwoActionsCancel(parent: parentWidget,
93 i18n("The document \"%1\" has been modified.\n"
94 "Do you want to save your changes or discard them?",
95 docName),
96 i18n("Close Document"),
97 primaryAction: KStandardGuiItem::save(),
98 secondaryAction: KStandardGuiItem::discard());
99
100 bool abortClose = false;
101 bool handled = false;
102
103 switch (res) {
104 case KMessageBox::PrimaryAction:
105 Q_EMIT sigQueryClose(handled: &handled, abortClosing: &abortClose);
106 if (!handled) {
107 if (d->m_url.isEmpty()) {
108 QUrl url = QFileDialog::getSaveFileUrl(parent: parentWidget);
109 if (url.isEmpty()) {
110 return false;
111 }
112
113 saveAs(url);
114 } else {
115 save();
116 }
117 } else if (abortClose) {
118 return false;
119 }
120 return waitSaveComplete();
121 case KMessageBox::SecondaryAction:
122 return true;
123 default: // case KMessageBox::Cancel :
124 return false;
125 }
126}
127
128bool ReadWritePart::closeUrl()
129{
130 abortLoad(); // just in case
131 if (isReadWrite() && isModified()) {
132 if (!queryClose()) {
133 return false;
134 }
135 }
136 // Not modified => ok and delete temp file.
137 return ReadOnlyPart::closeUrl();
138}
139
140bool ReadWritePart::closeUrl(bool promptToSave)
141{
142 return promptToSave ? closeUrl() : ReadOnlyPart::closeUrl();
143}
144
145bool ReadWritePart::save()
146{
147 Q_D(ReadWritePart);
148
149 d->m_saveOk = false;
150 if (d->m_file.isEmpty()) { // document was created empty
151 d->prepareSaving();
152 }
153 if (saveFile()) {
154 return saveToUrl();
155 } else {
156 Q_EMIT canceled(errMsg: QString());
157 }
158 return false;
159}
160
161bool ReadWritePart::saveAs(const QUrl &url)
162{
163 Q_D(ReadWritePart);
164
165 if (!url.isValid()) {
166 qCCritical(KPARTSLOG) << "saveAs: Malformed URL" << url;
167 return false;
168 }
169 d->m_duringSaveAs = true;
170 d->m_originalURL = d->m_url;
171 d->m_originalFilePath = d->m_file;
172 d->m_url = url; // Store where to upload in saveToURL
173 d->prepareSaving();
174 bool result = save(); // Save local file and upload local file
175 if (result) {
176 if (d->m_originalURL != d->m_url) {
177 Q_EMIT urlChanged(url: d->m_url);
178 }
179
180 Q_EMIT setWindowCaption(d->m_url.toDisplayString(options: QUrl::PreferLocalFile));
181 } else {
182 d->m_url = d->m_originalURL;
183 d->m_file = d->m_originalFilePath;
184 d->m_duringSaveAs = false;
185 d->m_originalURL = QUrl();
186 d->m_originalFilePath.clear();
187 }
188
189 return result;
190}
191
192// Set m_file correctly for m_url
193void ReadWritePartPrivate::prepareSaving()
194{
195 // Local file
196 if (m_url.isLocalFile()) {
197 if (m_bTemp) { // get rid of a possible temp file first
198 // (happens if previous url was remote)
199 QFile::remove(fileName: m_file);
200 m_bTemp = false;
201 }
202 m_file = m_url.toLocalFile();
203 } else {
204 // Remote file
205 // We haven't saved yet, or we did but locally - provide a temp file
206 if (m_file.isEmpty() || !m_bTemp) {
207 QTemporaryFile tempFile;
208 tempFile.setAutoRemove(false);
209 tempFile.open();
210 m_file = tempFile.fileName();
211 m_bTemp = true;
212 }
213 // otherwise, we already had a temp file
214 }
215}
216
217static inline bool makeHardLink(const QString &src, const QString &dest)
218{
219#ifndef Q_OS_WIN
220 return ::link(from: QFile::encodeName(fileName: src).constData(), to: QFile::encodeName(fileName: dest).constData()) == 0;
221#else
222 return CreateHardLinkW((LPCWSTR)dest.utf16(), (LPCWSTR)src.utf16(), nullptr) != 0;
223#endif
224}
225
226bool ReadWritePart::saveToUrl()
227{
228 Q_D(ReadWritePart);
229
230 if (d->m_url.isLocalFile()) {
231 setModified(false);
232 Q_EMIT completed();
233 // if m_url is a local file there won't be a temp file -> nothing to remove
234 Q_ASSERT(!d->m_bTemp);
235 d->m_saveOk = true;
236 d->m_duringSaveAs = false;
237 d->m_originalURL = QUrl();
238 d->m_originalFilePath.clear();
239 return true; // Nothing to do
240 } else {
241 if (d->m_uploadJob) {
242 QFile::remove(fileName: d->m_uploadJob->srcUrl().toLocalFile());
243 d->m_uploadJob->kill();
244 d->m_uploadJob = nullptr;
245 }
246 QTemporaryFile *tempFile = new QTemporaryFile();
247 tempFile->open();
248 QString uploadFile = tempFile->fileName();
249 delete tempFile;
250 QUrl uploadUrl = QUrl::fromLocalFile(localfile: uploadFile);
251 // Create hardlink
252 if (!makeHardLink(src: d->m_file, dest: uploadFile)) {
253 // Uh oh, some error happened.
254 return false;
255 }
256 d->m_uploadJob = KIO::file_move(src: uploadUrl, dest: d->m_url, permissions: -1, flags: KIO::Overwrite);
257 KJobWidgets::setWindow(job: d->m_uploadJob, widget: widget());
258
259 connect(sender: d->m_uploadJob, signal: &KJob::result, context: this, slot: [d](KJob *job) {
260 d->slotUploadFinished(job);
261 });
262
263 return true;
264 }
265}
266
267void ReadWritePartPrivate::slotUploadFinished(KJob *)
268{
269 Q_Q(ReadWritePart);
270
271 if (m_uploadJob->error()) {
272 QFile::remove(fileName: m_uploadJob->srcUrl().toLocalFile());
273 QString error = m_uploadJob->errorString();
274 m_uploadJob = nullptr;
275 if (m_duringSaveAs) {
276 q->setUrl(m_originalURL);
277 m_file = m_originalFilePath;
278 }
279 Q_EMIT q->canceled(errMsg: error);
280 } else {
281#if HAVE_KDIRNOTIFY
282 ::org::kde::KDirNotify::emitFilesAdded(directory: m_url.adjusted(options: QUrl::RemoveFilename));
283#endif
284
285 m_uploadJob = nullptr;
286 q->setModified(false);
287 Q_EMIT q->completed();
288 m_saveOk = true;
289 }
290 m_duringSaveAs = false;
291 m_originalURL = QUrl();
292 m_originalFilePath.clear();
293 if (m_waitForSave) {
294 m_eventLoop.quit();
295 }
296}
297
298bool ReadWritePart::isReadWrite() const
299{
300 Q_D(const ReadWritePart);
301
302 return d->m_bReadWrite;
303}
304
305bool ReadWritePart::isModified() const
306{
307 Q_D(const ReadWritePart);
308
309 return d->m_bModified;
310}
311
312bool ReadWritePart::waitSaveComplete()
313{
314 Q_D(ReadWritePart);
315
316 if (!d->m_uploadJob) {
317 return d->m_saveOk;
318 }
319
320 d->m_waitForSave = true;
321
322 d->m_eventLoop.exec(flags: QEventLoop::ExcludeUserInputEvents);
323
324 d->m_waitForSave = false;
325
326 return d->m_saveOk;
327}
328
329#include "moc_readwritepart.cpp"
330

source code of kparts/src/readwritepart.cpp