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"