File indexing completed on 2024-05-05 04:22:01
0001 // SPDX-FileCopyrightText: 2004 Andrew Coles <andrew.i.coles@googlemail.com> 0002 // SPDX-FileCopyrightText: 2004-2007 Laurent Montel <montel@kde.org> 0003 // SPDX-FileCopyrightText: 2004-2005 Stephan Binner <binner@kde.org> 0004 // SPDX-FileCopyrightText: 2004-2022 Jesper K. Pedersen <jesper.pedersen@kdab.com> 0005 // SPDX-FileCopyrightText: 2005-2007 Dirk Mueller <mueller@kde.org> 0006 // SPDX-FileCopyrightText: 2007-2011 Jan Kundrát <jkt@flaska.net> 0007 // SPDX-FileCopyrightText: 2008 Henner Zeller <h.zeller@acm.org> 0008 // SPDX-FileCopyrightText: 2008-2010 Tuomas Suutari <tuomas@nepnep.net> 0009 // SPDX-FileCopyrightText: 2009 Hassan Ibraheem <hasan.ibraheem@gmail.com> 0010 // SPDX-FileCopyrightText: 2012-2013 Miika Turkia <miika.turkia@gmail.com> 0011 // SPDX-FileCopyrightText: 2012-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0012 // SPDX-FileCopyrightText: 2013 Pino Toscano <pino@kde.org> 0013 // SPDX-FileCopyrightText: 2016-2019 Tobias Leupold <tl@stonemx.de> 0014 // SPDX-FileCopyrightText: 2018 Antoni Bella Pérez <antonibella5@yahoo.com> 0015 // SPDX-FileCopyrightText: 2018 Yuri Chornoivan <yurchor@ukr.net> 0016 // 0017 // SPDX-License-Identifier: GPL-2.0-or-later 0018 0019 #include "Export.h" 0020 0021 #include "XMLHandler.h" 0022 0023 #include <DB/ImageDB.h> 0024 #include <DB/ImageInfo.h> 0025 #include <ImageManager/AsyncLoader.h> 0026 #include <ImageManager/RawImageDecoder.h> 0027 #include <Utilities/FileUtil.h> 0028 #include <kpabase/FileExtensions.h> 0029 #include <kpabase/FileNameList.h> 0030 #include <kpabase/FileNameUtil.h> 0031 0032 #include <KConfigGroup> 0033 #include <KHelpClient> 0034 #include <KLocalizedString> 0035 #include <KMessageBox> 0036 #include <KZip> 0037 #include <QApplication> 0038 #include <QBuffer> 0039 #include <QCheckBox> 0040 #include <QDialogButtonBox> 0041 #include <QFileDialog> 0042 #include <QFileInfo> 0043 #include <QGroupBox> 0044 #include <QLayout> 0045 #include <QProgressDialog> 0046 #include <QPushButton> 0047 #include <QRadioButton> 0048 #include <QSpinBox> 0049 #include <QVBoxLayout> 0050 0051 using namespace ImportExport; 0052 0053 namespace 0054 { 0055 bool isRAW(const DB::FileName &fileName) 0056 { 0057 return KPABase::isUsableRawImage(fileName); 0058 } 0059 } // namespace 0060 0061 void Export::imageExport(const DB::FileNameList &list) 0062 { 0063 ExportConfig config; 0064 if (config.exec() == QDialog::Rejected) 0065 return; 0066 0067 int maxSize = -1; 0068 if (config.mp_enforeMaxSize->isChecked()) 0069 maxSize = config.mp_maxSize->value(); 0070 0071 // Ask for zip file name 0072 QString zipFile = QFileDialog::getSaveFileName( 0073 nullptr, /* parent */ 0074 i18n("Save an export file"), /* caption */ 0075 QString(), /* directory */ 0076 i18n("KPhotoAlbum import files") + QLatin1String("(*.kim)") /*filter*/ 0077 ); 0078 if (zipFile.isNull()) 0079 return; 0080 0081 bool ok; 0082 Export *exp = new Export(list, zipFile, config.mp_compress->isChecked(), maxSize, config.imageFileLocation(), QString(), config.mp_generateThumbnails->isChecked(), &ok); 0083 delete exp; // It will not return before done - we still need a class to connect slots etc. 0084 0085 if (ok) 0086 showUsageDialog(); 0087 } 0088 0089 // PENDING(blackie) add warning if images are to be copied into a non empty directory. 0090 ExportConfig::ExportConfig() 0091 { 0092 setWindowTitle(i18nc("@title:window", "Export Configuration / Copy Images")); 0093 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help); 0094 QWidget *mainWidget = new QWidget(this); 0095 QVBoxLayout *mainLayout = new QVBoxLayout; 0096 setLayout(mainLayout); 0097 mainLayout->addWidget(mainWidget); 0098 0099 QWidget *top = new QWidget; 0100 mainLayout->addWidget(top); 0101 0102 QVBoxLayout *lay1 = new QVBoxLayout(top); 0103 0104 // Include images 0105 QGroupBox *grp = new QGroupBox(i18n("How to Handle Images")); 0106 lay1->addWidget(grp); 0107 0108 QVBoxLayout *boxLay = new QVBoxLayout(grp); 0109 m_include = new QRadioButton(i18n("Include in .kim file"), grp); 0110 m_manually = new QRadioButton(i18n("Do not copy files, only generate .kim file"), grp); 0111 m_auto = new QRadioButton(i18n("Automatically copy next to .kim file"), grp); 0112 m_link = new QRadioButton(i18n("Hard link next to .kim file"), grp); 0113 m_symlink = new QRadioButton(i18n("Symbolic link next to .kim file"), grp); 0114 m_auto->setChecked(true); 0115 0116 boxLay->addWidget(m_include); 0117 boxLay->addWidget(m_manually); 0118 boxLay->addWidget(m_auto); 0119 boxLay->addWidget(m_link); 0120 boxLay->addWidget(m_symlink); 0121 0122 // Compress 0123 mp_compress = new QCheckBox(i18n("Compress export file"), top); 0124 lay1->addWidget(mp_compress); 0125 0126 // Generate thumbnails 0127 mp_generateThumbnails = new QCheckBox(i18n("Generate thumbnails"), top); 0128 mp_generateThumbnails->setChecked(false); 0129 lay1->addWidget(mp_generateThumbnails); 0130 0131 // Enforece max size 0132 QHBoxLayout *hlay = new QHBoxLayout; 0133 lay1->addLayout(hlay); 0134 0135 mp_enforeMaxSize = new QCheckBox(i18n("Limit maximum image dimensions to: ")); 0136 hlay->addWidget(mp_enforeMaxSize); 0137 0138 mp_maxSize = new QSpinBox; 0139 mp_maxSize->setRange(100, 4000); 0140 0141 hlay->addWidget(mp_maxSize); 0142 mp_maxSize->setValue(800); 0143 0144 connect(mp_enforeMaxSize, &QCheckBox::toggled, mp_maxSize, &QSpinBox::setEnabled); 0145 mp_maxSize->setEnabled(false); 0146 0147 QString txt = i18n("<p>If your images are stored in a non-compressed file format then you may check this; " 0148 "otherwise, this just wastes time during import and export operations.</p>" 0149 "<p>In other words, do not check this if your images are stored in jpg, png or gif; but do check this " 0150 "if your images are stored in tiff.</p>"); 0151 mp_compress->setWhatsThis(txt); 0152 0153 txt = i18n("<p>Generate thumbnail images</p>"); 0154 mp_generateThumbnails->setWhatsThis(txt); 0155 0156 txt = i18n("<p>With this option you may limit the maximum dimensions (width and height) of your images. " 0157 "Doing so will make the resulting export file smaller, but will of course also make the quality " 0158 "worse if someone wants to see the exported images with larger dimensions.</p>"); 0159 0160 mp_enforeMaxSize->setWhatsThis(txt); 0161 mp_maxSize->setWhatsThis(txt); 0162 0163 txt = i18n("<p>When exporting images, bear in mind that there are two things the " 0164 "person importing these images again will need:<br/>" 0165 "1) meta information (image content, date etc.)<br/>" 0166 "2) the images themselves.</p>" 0167 0168 "<p>The images themselves can either be placed next to the .kim file, " 0169 "or copied into the .kim file. Copying the images into the .kim file works well " 0170 "for a recipient who wants all, or most of those images, for example " 0171 "when emailing a whole group of images. However, when you place the " 0172 "images on the Web, a lot of people will see them but most likely only " 0173 "download a few of them. It works better in this kind of case, to " 0174 "separate the images and the .kim file, by place them next to each " 0175 "other, so the user can access the images s/he wants.</p>"); 0176 0177 grp->setWhatsThis(txt); 0178 m_include->setWhatsThis(txt); 0179 m_manually->setWhatsThis(txt); 0180 m_link->setWhatsThis(txt); 0181 m_symlink->setWhatsThis(txt); 0182 m_auto->setWhatsThis(txt); 0183 0184 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 0185 okButton->setDefault(true); 0186 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0187 connect(buttonBox, &QDialogButtonBox::accepted, this, &ExportConfig::accept); 0188 connect(buttonBox, &QDialogButtonBox::rejected, this, &ExportConfig::reject); 0189 mainLayout->addWidget(buttonBox); 0190 0191 QPushButton *helpButton = buttonBox->button(QDialogButtonBox::Help); 0192 connect(helpButton, &QPushButton::clicked, this, &ExportConfig::showHelp); 0193 } 0194 0195 ImageFileLocation ExportConfig::imageFileLocation() const 0196 { 0197 if (m_include->isChecked()) 0198 return Inline; 0199 else if (m_manually->isChecked()) 0200 return ManualCopy; 0201 else if (m_link->isChecked()) 0202 return Link; 0203 else if (m_symlink->isChecked()) 0204 return Symlink; 0205 else 0206 return AutoCopy; 0207 } 0208 0209 void ExportConfig::showHelp() 0210 { 0211 KHelpClient::invokeHelp(QStringLiteral("chp-importExport")); 0212 } 0213 0214 Export::~Export() 0215 { 0216 delete m_zip; 0217 delete m_eventLoop; 0218 } 0219 0220 Export::Export( 0221 const DB::FileNameList &list, 0222 const QString &zipFile, 0223 bool compress, 0224 int maxSize, 0225 ImageFileLocation location, 0226 const QString &baseUrl, 0227 bool doGenerateThumbnails, 0228 bool *ok) 0229 : m_internalOk(true) 0230 , m_ok(ok) 0231 , m_maxSize(maxSize) 0232 , m_location(location) 0233 , m_eventLoop(new QEventLoop) 0234 { 0235 if (ok == nullptr) 0236 ok = &m_internalOk; 0237 *ok = true; 0238 m_destdir = QFileInfo(zipFile).path(); 0239 m_zip = new KZip(zipFile); 0240 m_zip->setCompression(compress ? KZip::DeflateCompression : KZip::NoCompression); 0241 if (!m_zip->open(QIODevice::WriteOnly)) { 0242 KMessageBox::error(nullptr, i18n("Error creating zip file")); 0243 *ok = false; 0244 return; 0245 } 0246 0247 // Create progress dialog 0248 int total = 1; 0249 if (location != ManualCopy) 0250 total += list.size(); 0251 if (doGenerateThumbnails) 0252 total += list.size(); 0253 0254 m_steps = 0; 0255 m_progressDialog = new QProgressDialog(MainWindow::Window::theMainWindow()); 0256 m_progressDialog->setCancelButtonText(i18n("&Cancel")); 0257 m_progressDialog->setMaximum(total); 0258 0259 m_progressDialog->setValue(0); 0260 m_progressDialog->show(); 0261 0262 // Copy image files and generate thumbnails 0263 if (location != ManualCopy) { 0264 m_copyingFiles = true; 0265 copyImages(list); 0266 } 0267 0268 if (*m_ok && doGenerateThumbnails) { 0269 m_copyingFiles = false; 0270 generateThumbnails(list); 0271 } 0272 0273 if (*m_ok) { 0274 // Create the index.xml file 0275 m_progressDialog->setLabelText(i18n("Creating index file")); 0276 QByteArray indexml = XMLHandler().createIndexXML(list, baseUrl, m_location, &m_filenameMapper); 0277 m_zip->writeFile(QStringLiteral("index.xml"), indexml.data()); 0278 0279 m_steps++; 0280 m_progressDialog->setValue(m_steps); 0281 m_zip->close(); 0282 } 0283 } 0284 0285 void Export::generateThumbnails(const DB::FileNameList &list) 0286 { 0287 m_progressDialog->setLabelText(i18n("Creating thumbnails")); 0288 m_loopEntered = false; 0289 m_subdir = QLatin1String("Thumbnails/"); 0290 m_filesRemaining = list.size(); // Used to break the event loop. 0291 for (const DB::FileName &fileName : list) { 0292 const auto info = DB::ImageDB::instance()->info(fileName); 0293 ImageManager::ImageRequest *request = new ImageManager::ImageRequest(fileName, QSize(128, 128), info->angle(), this); 0294 request->setPriority(ImageManager::BatchTask); 0295 ImageManager::AsyncLoader::instance()->load(request); 0296 } 0297 if (m_filesRemaining > 0) { 0298 m_loopEntered = true; 0299 m_eventLoop->exec(); 0300 } 0301 } 0302 0303 void Export::copyImages(const DB::FileNameList &list) 0304 { 0305 Q_ASSERT(m_location != ManualCopy); 0306 0307 m_loopEntered = false; 0308 m_subdir = QLatin1String("Images/"); 0309 0310 m_progressDialog->setLabelText(i18n("Copying image files")); 0311 0312 m_filesRemaining = 0; 0313 for (const DB::FileName &fileName : list) { 0314 QString file = fileName.absolute(); 0315 QString zippedName = m_filenameMapper.uniqNameFor(fileName); 0316 0317 if (m_maxSize == -1 || KPABase::isVideo(fileName) || isRAW(fileName)) { 0318 const QFileInfo fileInfo(file); 0319 if (fileInfo.isSymLink()) { 0320 file = fileInfo.symLinkTarget(); 0321 } 0322 if (m_location == Inline) 0323 m_zip->addLocalFile(file, QStringLiteral("Images/") + zippedName); 0324 else if (m_location == AutoCopy) 0325 Utilities::copyOrOverwrite(file, m_destdir + QLatin1String("/") + zippedName); 0326 else if (m_location == Link) 0327 Utilities::makeHardLink(file, m_destdir + QLatin1String("/") + zippedName); 0328 else if (m_location == Symlink) 0329 Utilities::makeSymbolicLink(file, m_destdir + QLatin1String("/") + zippedName); 0330 0331 m_steps++; 0332 m_progressDialog->setValue(m_steps); 0333 } else { 0334 m_filesRemaining++; 0335 ImageManager::ImageRequest *request = new ImageManager::ImageRequest(DB::FileName::fromAbsolutePath(file), QSize(m_maxSize, m_maxSize), 0, this); 0336 request->setPriority(ImageManager::BatchTask); 0337 ImageManager::AsyncLoader::instance()->load(request); 0338 } 0339 0340 // Test if the cancel button was pressed. 0341 qApp->processEvents(QEventLoop::AllEvents); 0342 0343 if (m_progressDialog->wasCanceled()) { 0344 *m_ok = false; 0345 return; 0346 } 0347 } 0348 if (m_filesRemaining > 0) { 0349 m_loopEntered = true; 0350 m_eventLoop->exec(); 0351 } 0352 } 0353 0354 void Export::pixmapLoaded(ImageManager::ImageRequest *request, const QImage &image) 0355 { 0356 const DB::FileName fileName = request->databaseFileName(); 0357 if (!request->loadedOK()) 0358 return; 0359 0360 const QString ext = (KPABase::isVideo(fileName) || isRAW(fileName)) ? QStringLiteral("jpg") : QFileInfo(m_filenameMapper.uniqNameFor(fileName)).completeSuffix(); 0361 0362 // Add the file to the zip archive 0363 QString zipFileName = QStringLiteral("%1/%2.%3").arg(Utilities::stripEndingForwardSlash(m_subdir), QFileInfo(m_filenameMapper.uniqNameFor(fileName)).baseName(), ext); 0364 QByteArray data; 0365 QBuffer buffer(&data); 0366 buffer.open(QIODevice::WriteOnly); 0367 image.save(&buffer, QFileInfo(zipFileName).suffix().toLower().toLatin1().constData()); 0368 0369 if (m_location == Inline || !m_copyingFiles) 0370 m_zip->writeFile(zipFileName, data.constData()); 0371 else { 0372 QString file = m_destdir + QLatin1String("/") + m_filenameMapper.uniqNameFor(fileName); 0373 QFile out(file); 0374 if (!out.open(QIODevice::WriteOnly)) { 0375 KMessageBox::error(nullptr, i18n("Error writing file %1", file)); 0376 *m_ok = false; 0377 } 0378 out.write(data.constData(), data.size()); 0379 out.close(); 0380 } 0381 0382 qApp->processEvents(QEventLoop::AllEvents); 0383 0384 bool canceled = (!*m_ok || m_progressDialog->wasCanceled()); 0385 0386 if (canceled) { 0387 *m_ok = false; 0388 m_eventLoop->exit(); 0389 ImageManager::AsyncLoader::instance()->stop(this); 0390 return; 0391 } 0392 0393 m_steps++; 0394 m_filesRemaining--; 0395 m_progressDialog->setValue(m_steps); 0396 0397 if (m_filesRemaining == 0 && m_loopEntered) 0398 m_eventLoop->exit(); 0399 } 0400 0401 void Export::showUsageDialog() 0402 { 0403 QString txt = i18n("<p>Other KPhotoAlbum users may now load the import file into their database, by choosing <b>import</b> in " 0404 "the file menu.</p>" 0405 "<p>If they find it on a web site, and the web server is correctly configured, all they need to do is simply " 0406 "to click it from within konqueror. To enable this, your web server needs to be configured for KPhotoAlbum. You do so by adding " 0407 "the following line to <b>/etc/httpd/mime.types</b> or similar:" 0408 "<pre>application/vnd.kde.kphotoalbum-import kim</pre>" 0409 "This will make your web server tell konqueror that it is a KPhotoAlbum file when clicking on the link, " 0410 "otherwise the web server will just tell konqueror that it is a plain text file.</p>"); 0411 0412 KMessageBox::information(nullptr, txt, i18n("How to Use the Export File"), QStringLiteral("export_how_to_use_the_export_file")); 0413 } 0414 0415 // vi:expandtab:tabstop=4 shiftwidth=4: 0416 0417 #include "moc_Export.cpp"