File indexing completed on 2024-05-12 05:09:25

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     QString s = Tellico::NetAccess::lastErrorString();
0061     if(s.isEmpty()) {
0062       myLog() << "Can't download" << url_.toDisplayString(QUrl::PreferLocalFile);
0063     } else {
0064       myLog() << s;
0065     }
0066     if(!quiet_) {
0067       GUI::Proxy::sorry(s.isEmpty() ? i18n(errorLoad, url_.fileName()) : s);
0068     }
0069     return;
0070   }
0071 
0072   m_device = new QFile(m_filename);
0073   m_isValid = true;
0074 }
0075 
0076 FileHandler::FileRef::~FileRef() {
0077   if(!m_filename.isEmpty()) {
0078     Tellico::NetAccess::removeTempFile(m_filename);
0079   }
0080   if(m_device) {
0081     m_device->close();
0082   }
0083   delete m_device;
0084   m_device = nullptr;
0085   m_isValid = false;
0086 }
0087 
0088 bool FileHandler::FileRef::open(bool quiet_) {
0089   if(!isValid()) {
0090     return false;
0091   }
0092   if(!m_device || !m_device->open(QIODevice::ReadOnly)) {
0093     if(!quiet_) {
0094       QUrl u = QUrl::fromLocalFile(fileName());
0095       GUI::Proxy::sorry(i18n(errorLoad, u.fileName()));
0096     }
0097     delete m_device;
0098     m_device = nullptr;
0099     m_isValid = false;
0100     return false;
0101   }
0102   return true;
0103 }
0104 
0105 FileHandler::FileRef* FileHandler::fileRef(const QUrl& url_, bool quiet_) {
0106   return new FileRef(url_, quiet_);
0107 }
0108 
0109 QString FileHandler::readTextFile(const QUrl& url_, bool quiet_/*=false*/, bool useUTF8_ /*false*/) {
0110   FileRef f(url_, quiet_);
0111   if(!f.isValid()) {
0112     return QString();
0113   }
0114 
0115   if(f.open(quiet_)) {
0116     QTextStream stream(f.file());
0117     if(useUTF8_) {
0118       stream.setCodec("UTF-8");
0119     }
0120     return stream.readAll();
0121   }
0122   return QString();
0123 }
0124 
0125 QString FileHandler::readXMLFile(const QUrl& url_, bool quiet_/*=false*/) {
0126   FileRef f(url_, quiet_);
0127   if(!f.isValid()) {
0128     return QString();
0129   }
0130 
0131   if(f.open(quiet_)) {
0132     return XMLHandler::readXMLData(f.file()->readAll());
0133   }
0134   return QString();
0135 }
0136 
0137 QDomDocument FileHandler::readXMLDocument(const QUrl& url_, bool processNamespace_, bool quiet_) {
0138   FileRef f(url_, quiet_);
0139   if(!f.isValid()) {
0140     return QDomDocument();
0141   }
0142 
0143   QDomDocument doc;
0144   QString errorMsg;
0145   int errorLine, errorColumn;
0146   if(!f.open(quiet_)) {
0147     return QDomDocument();
0148   }
0149   if(!doc.setContent(f.file(), processNamespace_, &errorMsg, &errorLine, &errorColumn)) {
0150     if(!quiet_) {
0151       QString details = i18n("There is an XML parsing error in line %1, column %2.", errorLine, errorColumn);
0152       details += QLatin1String("\n");
0153       details += i18n("The error message from Qt is:");
0154       details += QLatin1String("\n\t") + errorMsg;
0155       GUI::CursorSaver cs(Qt::ArrowCursor);
0156       if(GUI::Proxy::widget()) {
0157         KMessageBox::detailedError(GUI::Proxy::widget(), i18n(errorLoad, url_.fileName()), details);
0158       }
0159     }
0160     return QDomDocument();
0161   }
0162   return doc;
0163 }
0164 
0165 QByteArray FileHandler::readDataFile(const QUrl& url_, bool quiet_) {
0166   FileRef f(url_, quiet_);
0167   if(!f.isValid()) {
0168     return QByteArray();
0169   }
0170 
0171   f.open(quiet_);
0172   return f.file()->readAll();
0173 }
0174 
0175 // TODO: really, this should be decoupled from the writeBackupFile() function
0176 // but every other function that calls it would need to be updated
0177 bool FileHandler::queryExists(const QUrl& url_) {
0178   if(url_.isEmpty() || !QFile::exists(url_.toLocalFile())) {
0179     return true;
0180   }
0181 
0182   // no need to check if we're actually overwriting the current url
0183   // the TellicoImporter forces the write
0184   GUI::CursorSaver cs(Qt::ArrowCursor);
0185   QString str = i18n("A file named \"%1\" already exists. "
0186                      "Are you sure you want to overwrite it?", url_.fileName());
0187   int want_continue = KMessageBox::warningContinueCancel(GUI::Proxy::widget(), str,
0188                                                          i18n("Overwrite File?"),
0189                                                          KStandardGuiItem::overwrite());
0190 
0191   if(want_continue == KMessageBox::Cancel) {
0192     return false;
0193   }
0194   return writeBackupFile(url_);
0195 }
0196 
0197 bool FileHandler::writeBackupFile(const QUrl& url_) {
0198   bool success = true;
0199   if(url_.isLocalFile()) {
0200     success = KBackup::simpleBackupFile(url_.toLocalFile());
0201   } else {
0202     QUrl backup(url_);
0203     backup.setPath(backup.toLocalFile() + QLatin1Char('~'));
0204     KIO::DeleteJob* delJob = KIO::del(backup);
0205     KJobWidgets::setWindow(delJob, GUI::Proxy::widget());
0206     delJob->exec(); // might fail if backup doesn't exist, that's ok
0207     KIO::FileCopyJob* job = KIO::file_copy(url_, backup, -1, KIO::Overwrite);
0208     KJobWidgets::setWindow(job, GUI::Proxy::widget());
0209     success = job->exec();
0210   }
0211   if(!success) {
0212     GUI::Proxy::sorry(i18n(errorWrite, url_.fileName() + QLatin1Char('~')));
0213   }
0214   return success;
0215 }
0216 
0217 bool FileHandler::writeTextURL(const QUrl& url_, const QString& text_, bool encodeUTF8_, bool force_, bool quiet_) {
0218   if((!force_ && !queryExists(url_)) || text_.isNull()) {
0219     return false;
0220   }
0221 
0222   if(url_.isLocalFile()) {
0223     QSaveFile f(url_.toLocalFile());
0224     f.open(QIODevice::WriteOnly);
0225     if(f.error() != QFile::NoError) {
0226       if(!quiet_) {
0227         GUI::Proxy::sorry(i18n(errorWrite, url_.fileName()));
0228       }
0229       return false;
0230     }
0231     return FileHandler::writeTextFile(f, text_, encodeUTF8_);
0232   }
0233 
0234   // save to remote file
0235   QTemporaryFile tempfile;
0236   tempfile.open();
0237   QSaveFile f(tempfile.fileName());
0238   f.open(QIODevice::WriteOnly);
0239   if(f.error() != QFile::NoError) {
0240     tempfile.remove();
0241     if(!quiet_) {
0242       GUI::Proxy::sorry(i18n(errorWrite, url_.fileName()));
0243     }
0244     return false;
0245   }
0246 
0247   bool success = FileHandler::writeTextFile(f, text_, encodeUTF8_);
0248   if(success) {
0249     KIO::Job* job = KIO::file_copy(QUrl::fromLocalFile(tempfile.fileName()), url_, -1, KIO::Overwrite);
0250     KJobWidgets::setWindow(job, GUI::Proxy::widget());
0251     success = job->exec();
0252     if(!success && !quiet_) {
0253       GUI::Proxy::sorry(i18n(errorUpload, url_.fileName()));
0254     }
0255   }
0256   tempfile.remove();
0257 
0258   return success;
0259 }
0260 
0261 bool FileHandler::writeTextFile(QSaveFile& file_, const QString& text_, bool encodeUTF8_) {
0262   QTextStream ts(&file_);
0263   if(encodeUTF8_) {
0264     ts.setCodec("UTF-8");
0265   }
0266   // KDE Bug 380832. If string is longer than MAX_TEXT_CHUNK_WRITE_SIZE characters, split into chunks.
0267   for(int i = 0; i < text_.length(); i += MAX_TEXT_CHUNK_WRITE_SIZE) {
0268     ts << text_.midRef(i, MAX_TEXT_CHUNK_WRITE_SIZE);
0269   }
0270   file_.flush();
0271   const bool success = file_.commit();
0272   if(!success) {
0273     myLog() << "Failed to write text file:" << file_.error();
0274   }
0275   return success;
0276 }
0277 
0278 bool FileHandler::writeDataURL(const QUrl& url_, const QByteArray& data_, bool force_, bool quiet_) {
0279   if(!force_ && !queryExists(url_)) {
0280     return false;
0281   }
0282 
0283   if(url_.isLocalFile()) {
0284     QSaveFile f(url_.toLocalFile());
0285     f.open(QIODevice::WriteOnly);
0286     if(f.error() != QFile::NoError) {
0287       if(!quiet_) {
0288         GUI::Proxy::sorry(i18n(errorWrite, url_.fileName()));
0289       }
0290       return false;
0291     }
0292     return FileHandler::writeDataFile(f, data_);
0293   }
0294 
0295   // save to remote file
0296   QTemporaryFile tempfile;
0297   tempfile.open();
0298   QSaveFile f(tempfile.fileName());
0299   f.open(QIODevice::WriteOnly);
0300   if(f.error() != QFile::NoError) {
0301     if(!quiet_) {
0302       GUI::Proxy::sorry(i18n(errorWrite, url_.fileName()));
0303     }
0304     return false;
0305   }
0306 
0307   bool success = FileHandler::writeDataFile(f, data_);
0308   if(success) {
0309     KIO::Job* job = KIO::file_copy(QUrl::fromLocalFile(tempfile.fileName()), url_, -1, KIO::Overwrite);
0310     KJobWidgets::setWindow(job, GUI::Proxy::widget());
0311     success = job->exec();
0312     if(!success && !quiet_) {
0313       GUI::Proxy::sorry(i18n(errorUpload, url_.fileName()));
0314     }
0315   }
0316   tempfile.remove();
0317 
0318   return success;
0319 }
0320 
0321 bool FileHandler::writeDataFile(QSaveFile& file_, const QByteArray& data_) {
0322   QDataStream s(&file_);
0323   s.writeRawData(data_.data(), data_.size());
0324   file_.flush();
0325   const bool success = file_.commit();
0326   if(!success) {
0327     myDebug() << "Failed to write data file:" << file_.error();
0328   }
0329   return success;
0330 }