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

source code of kparts/src/readwritepart.cpp