File indexing completed on 2024-05-12 15:55:06

0001 /************************************************************************
0002  *                                  *
0003  *  This file is part of Kooka, a scanning/OCR application using    *
0004  *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.  *
0005  *                                  *
0006  *  Copyright (C) 2021      Jonathan Marten <jjm@keelhaul.me.uk>    *
0007  *                                  *
0008  *  Kooka is free software; you can redistribute it and/or modify it    *
0009  *  under the terms of the GNU Library General Public License as    *
0010  *  published by the Free Software Foundation and appearing in the  *
0011  *  file COPYING included in the packaging of this file;  either    *
0012  *  version 2 of the License, or (at your option) any later version.    *
0013  *                                  *
0014  *  As a special exception, permission is given to link this program    *
0015  *  with any version of the KADMOS OCR/ICR engine (a product of     *
0016  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting  *
0017  *  executable without including the source code for KADMOS in the  *
0018  *  source distribution.                        *
0019  *                                  *
0020  *  This program is distributed in the hope that it will be useful, *
0021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of  *
0022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *
0023  *  GNU General Public License for more details.            *
0024  *                                  *
0025  *  You should have received a copy of the GNU General Public       *
0026  *  License along with this program;  see the file COPYING.  If     *
0027  *  not, see <http://www.gnu.org/licenses/>.                *
0028  *                                  *
0029  ************************************************************************/
0030 
0031 #include "destinationsave.h"
0032 
0033 #include <qfiledialog.h>
0034 #include <qmimetype.h>
0035 #include <qmimedatabase.h>
0036 
0037 #include <kpluginfactory.h>
0038 #include <klocalizedstring.h>
0039 #include <kmessagebox.h>
0040 #include <kprotocolinfo.h>
0041 
0042 #include <kio/statjob.h>
0043 #include <kio/copyjob.h>
0044 #include <kio/jobuidelegatefactory.h>
0045 
0046 #include "scanparamspage.h"
0047 #include "kookasettings.h"
0048 #include "imageformat.h"
0049 #include "formatdialog.h"
0050 #include "imgsaver.h"
0051 #include "destination_logging.h"
0052 
0053 
0054 K_PLUGIN_FACTORY_WITH_JSON(DestinationSaveFactory, "kookadestination-save.json", registerPlugin<DestinationSave>();)
0055 #include "destinationsave.moc"
0056 
0057 
0058 DestinationSave::DestinationSave(QObject *pnt, const QVariantList &args)
0059     : AbstractDestination(pnt, "DestinationSave")
0060 {
0061 }
0062 
0063 
0064 bool DestinationSave::scanStarting(ScanImage::ImageType type)
0065 {
0066     mSaveUrl.clear();                   // reset for a new scan
0067     mSaveMime.clear();
0068 
0069     // Honour the "Ask for filename before/after scan" setting
0070     if (KookaSettings::saverAskBeforeScan())
0071     {
0072         if (!getSaveLocation(type)) return (false);
0073     }
0074 
0075     return (true);
0076 }
0077 
0078 
0079 void DestinationSave::imageScanned(ScanImage::Ptr img)
0080 {
0081     qCDebug(DESTINATION_LOG) << "received image size" << img->size();
0082 
0083     if (!mSaveUrl.isValid())                // if we didn't ask before,
0084     {                           // then do it now
0085         if (!getSaveLocation(img->imageType())) return;
0086     }
0087     if (!mSaveUrl.isValid()) return;            // nowhere to save to
0088 
0089     ImageFormat fmt = getSaveFormat(mSaveMime, img);
0090     qCDebug(DESTINATION_LOG) << "to" << mSaveUrl << "mime" << mSaveMime << "format" << fmt;
0091     if (!fmt.isValid()) return;             // should never happen
0092 
0093     // TODO: move from here on into ImgSaver, so that it can save to
0094     // remote locations.
0095     QUrl url = mSaveUrl;
0096     if (!url.isLocalFile())
0097     {
0098         if (KProtocolInfo::protocolClass(url.scheme())==QLatin1String(":local"))
0099         {
0100             qCDebug(DESTINATION_LOG) << "maybe local, running a StatJob";
0101 
0102             // Even though the purpose of the StatJob is just to get information,
0103             // an error is displayed if the destination file does not exist (as
0104             // it is assumed it does not at this stage).  To avoid this situation
0105             // but to still get errors and/or warnings for other problems (e.g.
0106             // an unreachable host, authentication failiure etc), give the StatJob
0107             // the URL of the containing directory and add the file name back
0108             // afterwards.
0109             KIO::StatJob *job = KIO::mostLocalUrl(url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash));
0110             job->setSide(KIO::StatJob::DestinationSide);
0111             job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, parentWidget()));
0112             if (job->exec())
0113             {
0114                 QString fileName = url.fileName();
0115                 url = job->mostLocalUrl();
0116                 url.setPath(url.path()+'/'+fileName);
0117                 qDebug() << "StatJob result" << url;
0118             }
0119         }
0120     }
0121 
0122     if (url.isValid() && url.isLocalFile())     // did we find a local path?
0123     {
0124         // The save location is local, so we can use ImgSaver directly.
0125         qCDebug(DESTINATION_LOG) << "save to local" << url.toLocalFile();
0126 
0127         ImgSaver saver(url.adjusted(QUrl::RemoveFilename));
0128         ImgSaver::ImageSaveStatus status = saver.saveImage(img, url, fmt);
0129         if (status!=ImgSaver::SaveStatusOk)
0130         {
0131             KMessageBox::error(parentWidget(),
0132                                xi18nc("@info", "Cannot save image to <filename>%1</filename><nl/><message>%2</message>",
0133                                       url.toDisplayString(), saver.errorString(status)),
0134                                i18n("Cannot Save Image"));
0135         }
0136     }
0137     else                        // the location is not local
0138     {
0139         qCDebug(DESTINATION_LOG) << "save to remote";
0140 
0141         // The save location is remote.  Save a temporary image,
0142         // then use KIO to copy it to the destination location.
0143         QUrl url = saveTempImage(fmt, img);     // save a temporary image
0144         if (!url.isValid()) return;         // temp image save failed
0145 
0146         KIO::CopyJob *job = KIO::copy(url, mSaveUrl);
0147         job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, parentWidget()));
0148         job->exec();                    // do the remote copy
0149 
0150         QFile::remove(url.toLocalFile());       // remove the temporary file
0151     }
0152 
0153     // Remember the values used, so that starting a new scan and then
0154     // cancelling it will not clear them.  See scanStarting() and
0155     // saveSettings().
0156     mLastSaveUrl = mSaveUrl.adjusted(QUrl::RemoveFilename);
0157     mLastSaveMime = mSaveMime;
0158 }
0159 
0160 
0161 KLocalizedString DestinationSave::scanDestinationString()
0162 {
0163     if (!mSaveUrl.isValid()) return (ki18n("Saving image"));
0164     return (kxi18n("Saving to <filename>%1</filename>").subs(mSaveUrl.toDisplayString()));
0165 }
0166 
0167 
0168 void DestinationSave::saveSettings() const
0169 {
0170     if (mLastSaveUrl.isValid()) KookaSettings::setDestinationSaveDest(mLastSaveUrl);
0171     if (!mLastSaveMime.isEmpty()) KookaSettings::setDestinationSaveMime(mLastSaveMime);
0172 }
0173 
0174 
0175 bool DestinationSave::getSaveLocation(ScanImage::ImageType type)
0176 {
0177     qCDebug(DESTINATION_LOG) << "for type" << type;
0178 
0179     // Using the non-static QFileDialog so as to be able to
0180     // set MIME type filters.
0181     QFileDialog dlg(parentWidget(), i18n("Save Scan"));
0182     dlg.setAcceptMode(QFileDialog::AcceptSave);
0183 
0184     // Check whether to offer only recommended save formats for
0185     // the image type.
0186     const bool recOnly = KookaSettings::saverOnlyRecommendedTypes();
0187 
0188     QStringList filters;
0189     const QList<QMimeType> *mimeTypes = ImageFormat::mimeTypes();
0190     for (const QMimeType &mimeType : *mimeTypes)
0191     {
0192         ImageFormat fmt = ImageFormat::formatForMime(mimeType);
0193         if (!fmt.isValid()) continue;           // format for that MIME type
0194                             // see if it should be used
0195         if (!FormatDialog::isCompatible(mimeType, type, recOnly)) continue;
0196         filters << mimeType.name();         // yes, add to filter list
0197     }
0198 
0199     // To allow the user to save to a format for which there may not
0200     // be a filter entry, the "All files" type is added.  If this filter is
0201     // selected when the dialogue completes, the applicable MIME type is
0202     // deduced from the full save URL.
0203     filters << "application/octet-stream";
0204 
0205     dlg.setMimeTypeFilters(filters);
0206 
0207     QString saveMime = mLastSaveMime;
0208     if (saveMime.isEmpty()) saveMime = KookaSettings::destinationSaveMime();
0209     if (!saveMime.isEmpty())
0210     {
0211         if (filters.contains(saveMime)) dlg.selectMimeTypeFilter(saveMime);
0212         else dlg.selectMimeTypeFilter(filters.last());
0213     }
0214 
0215     QUrl saveUrl = mLastSaveUrl;
0216     if (!saveUrl.isValid()) saveUrl = KookaSettings::destinationSaveDest();
0217     if (saveUrl.isValid()) dlg.setDirectoryUrl(saveUrl);
0218 
0219     if (!dlg.exec()) return (false);
0220 
0221     const QList<QUrl> urls = dlg.selectedUrls();
0222     if (urls.isEmpty()) return (false);
0223 
0224     mSaveUrl = urls.first();
0225 
0226     mSaveMime = dlg.selectedMimeTypeFilter();
0227     // If the "All files" filter is selected, deduce the applicable MIME type
0228     // from the save URL.
0229     if (mSaveMime==filters.last())
0230     {
0231         QMimeDatabase db;
0232         QMimeType mimeType = db.mimeTypeForUrl(mSaveUrl);
0233         ImageFormat fmt = ImageFormat::formatForMime(mimeType);
0234         if (!fmt.isValid())
0235         {
0236             KMessageBox::error(parentWidget(),
0237                                xi18nc("@info", "Cannot save to <filename>%2</filename><nl/>The image format <resource>%1</resource> is not supported.",
0238                                       mimeType.name(), mSaveUrl.toDisplayString()),
0239                                i18n("Cannot Save Image"));
0240             return (false);
0241         }
0242 
0243         mSaveMime = mimeType.name();            // use resolved MIME type
0244     }
0245 
0246     qCDebug(DESTINATION_LOG) << "url" << mSaveUrl << "mime" << mSaveMime;
0247     return (true);
0248 }