File indexing completed on 2024-04-28 15:29:23

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
0004     SPDX-FileCopyrightText: 1999-2005 David Faure <faure@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "readwritepart.h"
0010 #include "readwritepart_p.h"
0011 
0012 #include "kparts_logging.h"
0013 
0014 #include <KDirNotify>
0015 #include <KIO/FileCopyJob>
0016 #include <KJobWidgets>
0017 #include <KLocalizedString>
0018 #include <KMessageBox>
0019 
0020 #include <QApplication>
0021 #include <QFileDialog>
0022 #include <QTemporaryFile>
0023 
0024 #include <qplatformdefs.h>
0025 
0026 #ifdef Q_OS_WIN
0027 #include <qt_windows.h> //CreateHardLink()
0028 #endif
0029 
0030 using namespace KParts;
0031 
0032 ReadWritePart::ReadWritePart(QObject *parent)
0033     : ReadOnlyPart(*new ReadWritePartPrivate(this), parent)
0034 {
0035 }
0036 
0037 ReadWritePart::~ReadWritePart()
0038 {
0039     // parent destructor will delete temp file
0040     // we can't call our own closeUrl() here, because
0041     // "cancel" wouldn't cancel anything. We have to assume
0042     // the app called closeUrl() before destroying us.
0043 }
0044 
0045 void ReadWritePart::setReadWrite(bool readwrite)
0046 {
0047     Q_D(ReadWritePart);
0048 
0049     // Perhaps we should check isModified here and issue a warning if true
0050     d->m_bReadWrite = readwrite;
0051 }
0052 
0053 void ReadWritePart::setModified(bool modified)
0054 {
0055     Q_D(ReadWritePart);
0056 
0057     // qDebug() << "setModified(" << (modified ? "true" : "false") << ")";
0058     if (!d->m_bReadWrite && modified) {
0059         qCCritical(KPARTSLOG) << "Can't set a read-only document to 'modified' !";
0060         return;
0061     }
0062     d->m_bModified = modified;
0063 }
0064 
0065 void ReadWritePart::setModified()
0066 {
0067     setModified(true);
0068 }
0069 
0070 bool ReadWritePart::queryClose()
0071 {
0072     Q_D(ReadWritePart);
0073 
0074     if (!isReadWrite() || !isModified()) {
0075         return true;
0076     }
0077 
0078     QString docName = url().fileName();
0079     if (docName.isEmpty()) {
0080         docName = i18n("Untitled");
0081     }
0082 
0083     QWidget *parentWidget = widget();
0084     if (!parentWidget) {
0085         parentWidget = QApplication::activeWindow();
0086     }
0087 
0088     int res = KMessageBox::warningTwoActionsCancel(parentWidget,
0089                                                    i18n("The document \"%1\" has been modified.\n"
0090                                                         "Do you want to save your changes or discard them?",
0091                                                         docName),
0092                                                    i18n("Close Document"),
0093                                                    KStandardGuiItem::save(),
0094                                                    KStandardGuiItem::discard());
0095 
0096     bool abortClose = false;
0097     bool handled = false;
0098 
0099     switch (res) {
0100     case KMessageBox::PrimaryAction:
0101         Q_EMIT sigQueryClose(&handled, &abortClose);
0102         if (!handled) {
0103             if (d->m_url.isEmpty()) {
0104                 QUrl url = QFileDialog::getSaveFileUrl(parentWidget);
0105                 if (url.isEmpty()) {
0106                     return false;
0107                 }
0108 
0109                 saveAs(url);
0110             } else {
0111                 save();
0112             }
0113         } else if (abortClose) {
0114             return false;
0115         }
0116         return waitSaveComplete();
0117     case KMessageBox::SecondaryAction:
0118         return true;
0119     default: // case KMessageBox::Cancel :
0120         return false;
0121     }
0122 }
0123 
0124 bool ReadWritePart::closeUrl()
0125 {
0126     abortLoad(); // just in case
0127     if (isReadWrite() && isModified()) {
0128         if (!queryClose()) {
0129             return false;
0130         }
0131     }
0132     // Not modified => ok and delete temp file.
0133     return ReadOnlyPart::closeUrl();
0134 }
0135 
0136 bool ReadWritePart::closeUrl(bool promptToSave)
0137 {
0138     return promptToSave ? closeUrl() : ReadOnlyPart::closeUrl();
0139 }
0140 
0141 bool ReadWritePart::save()
0142 {
0143     Q_D(ReadWritePart);
0144 
0145     d->m_saveOk = false;
0146     if (d->m_file.isEmpty()) { // document was created empty
0147         d->prepareSaving();
0148     }
0149     if (saveFile()) {
0150         return saveToUrl();
0151     } else {
0152         Q_EMIT canceled(QString());
0153     }
0154     return false;
0155 }
0156 
0157 bool ReadWritePart::saveAs(const QUrl &url)
0158 {
0159     Q_D(ReadWritePart);
0160 
0161     if (!url.isValid()) {
0162         qCCritical(KPARTSLOG) << "saveAs: Malformed URL" << url;
0163         return false;
0164     }
0165     d->m_duringSaveAs = true;
0166     d->m_originalURL = d->m_url;
0167     d->m_originalFilePath = d->m_file;
0168     d->m_url = url; // Store where to upload in saveToURL
0169     d->prepareSaving();
0170     bool result = save(); // Save local file and upload local file
0171     if (result) {
0172         if (d->m_originalURL != d->m_url) {
0173             Q_EMIT urlChanged(d->m_url);
0174         }
0175 
0176         Q_EMIT setWindowCaption(d->m_url.toDisplayString());
0177     } else {
0178         d->m_url = d->m_originalURL;
0179         d->m_file = d->m_originalFilePath;
0180         d->m_duringSaveAs = false;
0181         d->m_originalURL = QUrl();
0182         d->m_originalFilePath.clear();
0183     }
0184 
0185     return result;
0186 }
0187 
0188 // Set m_file correctly for m_url
0189 void ReadWritePartPrivate::prepareSaving()
0190 {
0191     // Local file
0192     if (m_url.isLocalFile()) {
0193         if (m_bTemp) { // get rid of a possible temp file first
0194             // (happens if previous url was remote)
0195             QFile::remove(m_file);
0196             m_bTemp = false;
0197         }
0198         m_file = m_url.toLocalFile();
0199     } else {
0200         // Remote file
0201         // We haven't saved yet, or we did but locally - provide a temp file
0202         if (m_file.isEmpty() || !m_bTemp) {
0203             QTemporaryFile tempFile;
0204             tempFile.setAutoRemove(false);
0205             tempFile.open();
0206             m_file = tempFile.fileName();
0207             m_bTemp = true;
0208         }
0209         // otherwise, we already had a temp file
0210     }
0211 }
0212 
0213 static inline bool makeHardLink(const QString &src, const QString &dest)
0214 {
0215 #ifndef Q_OS_WIN
0216     return ::link(QFile::encodeName(src).constData(), QFile::encodeName(dest).constData()) == 0;
0217 #else
0218     return CreateHardLinkW((LPCWSTR)dest.utf16(), (LPCWSTR)src.utf16(), nullptr) != 0;
0219 #endif
0220 }
0221 
0222 bool ReadWritePart::saveToUrl()
0223 {
0224     Q_D(ReadWritePart);
0225 
0226     if (d->m_url.isLocalFile()) {
0227         setModified(false);
0228         Q_EMIT completed();
0229         // if m_url is a local file there won't be a temp file -> nothing to remove
0230         Q_ASSERT(!d->m_bTemp);
0231         d->m_saveOk = true;
0232         d->m_duringSaveAs = false;
0233         d->m_originalURL = QUrl();
0234         d->m_originalFilePath.clear();
0235         return true; // Nothing to do
0236     } else {
0237         if (d->m_uploadJob) {
0238             QFile::remove(d->m_uploadJob->srcUrl().toLocalFile());
0239             d->m_uploadJob->kill();
0240             d->m_uploadJob = nullptr;
0241         }
0242         QTemporaryFile *tempFile = new QTemporaryFile();
0243         tempFile->open();
0244         QString uploadFile = tempFile->fileName();
0245         delete tempFile;
0246         QUrl uploadUrl = QUrl::fromLocalFile(uploadFile);
0247         // Create hardlink
0248         if (!makeHardLink(d->m_file, uploadFile)) {
0249             // Uh oh, some error happened.
0250             return false;
0251         }
0252         d->m_uploadJob = KIO::file_move(uploadUrl, d->m_url, -1, KIO::Overwrite);
0253         KJobWidgets::setWindow(d->m_uploadJob, widget());
0254 
0255         connect(d->m_uploadJob, &KJob::result, this, [d](KJob *job) {
0256             d->slotUploadFinished(job);
0257         });
0258 
0259         return true;
0260     }
0261 }
0262 
0263 void ReadWritePartPrivate::slotUploadFinished(KJob *)
0264 {
0265     Q_Q(ReadWritePart);
0266 
0267     if (m_uploadJob->error()) {
0268         QFile::remove(m_uploadJob->srcUrl().toLocalFile());
0269         QString error = m_uploadJob->errorString();
0270         m_uploadJob = nullptr;
0271         if (m_duringSaveAs) {
0272             q->setUrl(m_originalURL);
0273             m_file = m_originalFilePath;
0274         }
0275         Q_EMIT q->canceled(error);
0276     } else {
0277 #ifndef Q_OS_ANDROID
0278         ::org::kde::KDirNotify::emitFilesAdded(m_url.adjusted(QUrl::RemoveFilename));
0279 #endif
0280 
0281         m_uploadJob = nullptr;
0282         q->setModified(false);
0283         Q_EMIT q->completed();
0284         m_saveOk = true;
0285     }
0286     m_duringSaveAs = false;
0287     m_originalURL = QUrl();
0288     m_originalFilePath.clear();
0289     if (m_waitForSave) {
0290         m_eventLoop.quit();
0291     }
0292 }
0293 
0294 bool ReadWritePart::isReadWrite() const
0295 {
0296     Q_D(const ReadWritePart);
0297 
0298     return d->m_bReadWrite;
0299 }
0300 
0301 bool ReadWritePart::isModified() const
0302 {
0303     Q_D(const ReadWritePart);
0304 
0305     return d->m_bModified;
0306 }
0307 
0308 bool ReadWritePart::waitSaveComplete()
0309 {
0310     Q_D(ReadWritePart);
0311 
0312     if (!d->m_uploadJob) {
0313         return d->m_saveOk;
0314     }
0315 
0316     d->m_waitForSave = true;
0317 
0318     d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
0319 
0320     d->m_waitForSave = false;
0321 
0322     return d->m_saveOk;
0323 }
0324 
0325 #include "moc_readwritepart.cpp"