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 }