File indexing completed on 2024-05-12 16:45:40

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "filehandler.h"
0026 #include "tellico_strings.h"
0027 #include "netaccess.h"
0028 #include "../utils/cursorsaver.h"
0029 #include "../utils/guiproxy.h"
0030 #include "../utils/xmlhandler.h"
0031 #include "../tellico_debug.h"
0032 
0033 #include <KLocalizedString>
0034 #include <KMessageBox>
0035 #include <KFileItem>
0036 #include <KIO/FileCopyJob>
0037 #include <KIO/DeleteJob>
0038 #include <KBackup>
0039 #include <KJobWidgets>
0040 
0041 #include <QUrl>
0042 #include <QDomDocument>
0043 #include <QFile>
0044 #include <QTextStream>
0045 #include <QTemporaryFile>
0046 #include <QSaveFile>
0047 
0048 namespace {
0049   static const int MAX_TEXT_CHUNK_WRITE_SIZE = 100 * 1024 * 1024;
0050 }
0051 
0052 using Tellico::FileHandler;
0053 
0054 FileHandler::FileRef::FileRef(const QUrl& url_, bool quiet_) : m_device(nullptr), m_isValid(false) {
0055   if(url_.isEmpty()) {
0056     return;
0057   }
0058 
0059   if(!Tellico::NetAccess::download(url_, m_filename, GUI::Proxy::widget(), quiet_)) {
0060     myDebug() << "can't download" << url_.toDisplayString(QUrl::RemoveQuery);
0061     QString s = Tellico::NetAccess::lastErrorString();
0062     if(!s.isEmpty()) {
0063       myDebug() << s;
0064     }
0065     if(!quiet_) {
0066       GUI::Proxy::sorry(s.isEmpty() ? i18n(errorLoad, url_.fileName()) : s);
0067     }
0068     return;
0069   }
0070 
0071   m_device = new QFile(m_filename);
0072   m_isValid = true;
0073 }
0074 
0075 FileHandler::FileRef::~FileRef() {
0076   if(!m_filename.isEmpty()) {
0077     Tellico::NetAccess::removeTempFile(m_filename);
0078   }
0079   if(m_device) {
0080     m_device->close();
0081   }
0082   delete m_device;
0083   m_device = nullptr;
0084   m_isValid = false;
0085 }
0086 
0087 bool FileHandler::FileRef::open(bool quiet_) {
0088   if(!isValid()) {
0089     return false;
0090   }
0091   if(!m_device || !m_device->open(QIODevice::ReadOnly)) {
0092     if(!quiet_) {
0093       QUrl u = QUrl::fromLocalFile(fileName());
0094       GUI::Proxy::sorry(i18n(errorLoad, u.fileName()));
0095     }
0096     delete m_device;
0097     m_device = nullptr;
0098     m_isValid = false;
0099     return false;
0100   }
0101   return true;
0102 }
0103 
0104 FileHandler::FileRef* FileHandler::fileRef(const QUrl& url_, bool quiet_) {
0105   return new FileRef(url_, quiet_);
0106 }
0107 
0108 QString FileHandler::readTextFile(const QUrl& url_, bool quiet_/*=false*/, bool useUTF8_ /*false*/) {
0109   FileRef f(url_, quiet_);
0110   if(!f.isValid()) {
0111     return QString();
0112   }
0113 
0114   if(f.open(quiet_)) {
0115     QTextStream stream(f.file());
0116     if(useUTF8_) {
0117       stream.setCodec("UTF-8");
0118     }
0119     return stream.readAll();
0120   }
0121   return QString();
0122 }
0123 
0124 QString FileHandler::readXMLFile(const QUrl& url_, bool quiet_/*=false*/) {
0125   FileRef f(url_, quiet_);
0126   if(!f.isValid()) {
0127     return QString();
0128   }
0129 
0130   if(f.open(quiet_)) {
0131     return XMLHandler::readXMLData(f.file()->readAll());
0132   }
0133   return QString();
0134 }
0135 
0136 QDomDocument FileHandler::readXMLDocument(const QUrl& url_, bool processNamespace_, bool quiet_) {
0137   FileRef f(url_, quiet_);
0138   if(!f.isValid()) {
0139     return QDomDocument();
0140   }
0141 
0142   QDomDocument doc;
0143   QString errorMsg;
0144   int errorLine, errorColumn;
0145   if(!f.open(quiet_)) {
0146     return QDomDocument();
0147   }
0148   if(!doc.setContent(f.file(), processNamespace_, &errorMsg, &errorLine, &errorColumn)) {
0149     if(!quiet_) {
0150       QString details = i18n("There is an XML parsing error in line %1, column %2.", errorLine, errorColumn);
0151       details += QLatin1String("\n");
0152       details += i18n("The error message from Qt is:");
0153       details += QLatin1String("\n\t") + errorMsg;
0154       GUI::CursorSaver cs(Qt::ArrowCursor);
0155       if(GUI::Proxy::widget()) {
0156         KMessageBox::detailedSorry(GUI::Proxy::widget(), i18n(errorLoad, url_.fileName()), details);
0157       }
0158     }
0159     return QDomDocument();
0160   }
0161   return doc;
0162 }
0163 
0164 QByteArray FileHandler::readDataFile(const QUrl& url_, bool quiet_) {
0165   FileRef f(url_, quiet_);
0166   if(!f.isValid()) {
0167     return QByteArray();
0168   }
0169 
0170   f.open(quiet_);
0171   return f.file()->readAll();
0172 }
0173 
0174 // TODO: really, this should be decoupled from the writeBackupFile() function
0175 // but every other function that calls it would need to be updated
0176 bool FileHandler::queryExists(const QUrl& url_) {
0177   if(url_.isEmpty() || !QFile::exists(url_.toLocalFile())) {
0178     return true;
0179   }
0180 
0181   // no need to check if we're actually overwriting the current url
0182   // the TellicoImporter forces the write
0183   GUI::CursorSaver cs(Qt::ArrowCursor);
0184   QString str = i18n("A file named \"%1\" already exists. "
0185                      "Are you sure you want to overwrite it?", url_.fileName());
0186   int want_continue = KMessageBox::warningContinueCancel(GUI::Proxy::widget(), str,
0187                                                          i18n("Overwrite File?"),
0188                                                          KStandardGuiItem::overwrite());
0189 
0190   if(want_continue == KMessageBox::Cancel) {
0191     return false;
0192   }
0193   return writeBackupFile(url_);
0194 }
0195 
0196 bool FileHandler::writeBackupFile(const QUrl& url_) {
0197   bool success = true;
0198   if(url_.isLocalFile()) {
0199     success = KBackup::simpleBackupFile(url_.toLocalFile());
0200   } else {
0201     QUrl backup(url_);
0202     backup.setPath(backup.toLocalFile() + QLatin1Char('~'));
0203     KIO::DeleteJob* delJob = KIO::del(backup);
0204     KJobWidgets::setWindow(delJob, GUI::Proxy::widget());
0205     delJob->exec(); // might fail if backup doesn't exist, that's ok
0206     KIO::FileCopyJob* job = KIO::file_copy(url_, backup, -1, KIO::Overwrite);
0207     KJobWidgets::setWindow(job, GUI::Proxy::widget());
0208     success = job->exec();
0209   }
0210   if(!success) {
0211     GUI::Proxy::sorry(i18n(errorWrite, url_.fileName() + QLatin1Char('~')));
0212   }
0213   return success;
0214 }
0215 
0216 bool FileHandler::writeTextURL(const QUrl& url_, const QString& text_, bool encodeUTF8_, bool force_, bool quiet_) {
0217   if((!force_ && !queryExists(url_)) || text_.isNull()) {
0218     if(text_.isNull()) {
0219       myDebug() << "null string for" << url_;
0220     }
0221     return false;
0222   }
0223 
0224   if(url_.isLocalFile()) {
0225     QSaveFile f(url_.toLocalFile());
0226     f.open(QIODevice::WriteOnly);
0227     if(f.error() != QFile::NoError) {
0228       if(!quiet_) {
0229         GUI::Proxy::sorry(i18n(errorWrite, url_.fileName()));
0230       }
0231       return false;
0232     }
0233     return FileHandler::writeTextFile(f, text_, encodeUTF8_);
0234   }
0235 
0236   // save to remote file
0237   QTemporaryFile tempfile;
0238   tempfile.open();
0239   QSaveFile f(tempfile.fileName());
0240   f.open(QIODevice::WriteOnly);
0241   if(f.error() != QFile::NoError) {
0242     tempfile.remove();
0243     if(!quiet_) {
0244       GUI::Proxy::sorry(i18n(errorWrite, url_.fileName()));
0245     }
0246     return false;
0247   }
0248 
0249   bool success = FileHandler::writeTextFile(f, text_, encodeUTF8_);
0250   if(success) {
0251     KIO::Job* job = KIO::file_copy(QUrl::fromLocalFile(tempfile.fileName()), url_, -1, KIO::Overwrite);
0252     KJobWidgets::setWindow(job, GUI::Proxy::widget());
0253     success = job->exec();
0254     if(!success && !quiet_) {
0255       GUI::Proxy::sorry(i18n(errorUpload, url_.fileName()));
0256     }
0257   }
0258   tempfile.remove();
0259 
0260   return success;
0261 }
0262 
0263 bool FileHandler::writeTextFile(QSaveFile& file_, const QString& text_, bool encodeUTF8_) {
0264   QTextStream ts(&file_);
0265   if(encodeUTF8_) {
0266     ts.setCodec("UTF-8");
0267   }
0268   // KDE Bug 380832. If string is longer than MAX_TEXT_CHUNK_WRITE_SIZE characters, split into chunks.
0269   for(int i = 0; i < text_.length(); i += MAX_TEXT_CHUNK_WRITE_SIZE) {
0270     ts << text_.midRef(i, MAX_TEXT_CHUNK_WRITE_SIZE);
0271   }
0272   file_.flush();
0273   bool success = file_.commit();
0274 #ifndef NDEBUG
0275   if(!success) {
0276     myDebug() << "error = " << file_.error();
0277   }
0278 #endif
0279   return success;
0280 }
0281 
0282 bool FileHandler::writeDataURL(const QUrl& url_, const QByteArray& data_, bool force_, bool quiet_) {
0283   if(!force_ && !queryExists(url_)) {
0284     return false;
0285   }
0286 
0287   if(url_.isLocalFile()) {
0288     QSaveFile f(url_.toLocalFile());
0289     f.open(QIODevice::WriteOnly);
0290     if(f.error() != QFile::NoError) {
0291       if(!quiet_) {
0292         GUI::Proxy::sorry(i18n(errorWrite, url_.fileName()));
0293       }
0294       return false;
0295     }
0296     return FileHandler::writeDataFile(f, data_);
0297   }
0298 
0299   // save to remote file
0300   QTemporaryFile tempfile;
0301   tempfile.open();
0302   QSaveFile f(tempfile.fileName());
0303   f.open(QIODevice::WriteOnly);
0304   if(f.error() != QFile::NoError) {
0305     if(!quiet_) {
0306       GUI::Proxy::sorry(i18n(errorWrite, url_.fileName()));
0307     }
0308     return false;
0309   }
0310 
0311   bool success = FileHandler::writeDataFile(f, data_);
0312   if(success) {
0313     KIO::Job* job = KIO::file_copy(QUrl::fromLocalFile(tempfile.fileName()), url_, -1, KIO::Overwrite);
0314     KJobWidgets::setWindow(job, GUI::Proxy::widget());
0315     success = job->exec();
0316     if(!success && !quiet_) {
0317       GUI::Proxy::sorry(i18n(errorUpload, url_.fileName()));
0318     }
0319   }
0320   tempfile.remove();
0321 
0322   return success;
0323 }
0324 
0325 bool FileHandler::writeDataFile(QSaveFile& file_, const QByteArray& data_) {
0326 //  myDebug() << "Writing to" << file_.fileName();
0327   QDataStream s(&file_);
0328   s.writeRawData(data_.data(), data_.size());
0329   file_.flush();
0330   const bool success = file_.commit();
0331 #ifndef NDEBUG
0332   if(!success) {
0333     myDebug() << "error = " << file_.error();
0334   }
0335 #endif
0336   return success;
0337 }