diff --git a/core/dplugins/generic/view/slideshow/common/slideshowloader.cpp b/core/dplugins/generic/view/slideshow/common/slideshowloader.cpp index 1079d71529..8b97d99a8a 100644 --- a/core/dplugins/generic/view/slideshow/common/slideshowloader.cpp +++ b/core/dplugins/generic/view/slideshow/common/slideshowloader.cpp @@ -1,842 +1,842 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-04-21 * Description : slide show tool using preview of pictures. * * Copyright (C) 2005-2020 by Gilles Caulier * Copyright (C) 2004 by Enrico Ros * Copyright (C) 2019-2020 by Minh Nghia Duong * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "slideshowloader.h" #include "digikam_config.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_DBUS # include # include # include #endif // KDE includes #include // Local includes #include "digikam_debug.h" #include "slidetoolbar.h" #include "slideimage.h" #include "slideerror.h" #include "slideosd.h" #include "slideend.h" #ifdef HAVE_MEDIAPLAYER # include "slidevideo.h" #endif //HAVE_MEDIAPLAYER using namespace Digikam; namespace DigikamGenericSlideShowPlugin { class Q_DECL_HIDDEN SlideShowLoader::Private { public: explicit Private() : fileIndex(-1), screenSaverCookie(-1), mouseMoveTimer(nullptr), imageView(nullptr), #ifdef HAVE_MEDIAPLAYER videoView(nullptr), #endif errorView(nullptr), endView(nullptr), osd(nullptr), settings(nullptr) { } int fileIndex; int screenSaverCookie; QTimer* mouseMoveTimer; ///< To hide cursor when not moved. SlideImage* imageView; #ifdef HAVE_MEDIAPLAYER SlideVideo* videoView; #endif SlideError* errorView; SlideEnd* endView; SlideOSD* osd; SlideShowSettings* settings; QMap shortcutPrefixes; }; SlideShowLoader::SlideShowLoader(SlideShowSettings* const settings) : QStackedWidget(nullptr), d(new Private) { d->settings = settings; setAttribute(Qt::WA_DeleteOnClose); setWindowFlags(Qt::FramelessWindowHint); setContextMenuPolicy(Qt::PreventContextMenu); setWindowState(windowState() | Qt::WindowFullScreen); setWindowTitle(i18n("Slideshow")); setObjectName(QLatin1String("Slideshow")); setMouseTracking(true); // --------------------------------------------------------------- d->errorView = new SlideError(this); d->errorView->installEventFilter(this); insertWidget(ErrorView, d->errorView); // --------------------------------------------------------------- d->imageView = new SlideImage(this); d->imageView->setPreviewSettings(d->settings->previewSettings); d->imageView->installEventFilter(this); connect(d->imageView, SIGNAL(signalImageLoaded(bool)), this, SLOT(slotImageLoaded(bool))); insertWidget(ImageView, d->imageView); // --------------------------------------------------------------- #ifdef HAVE_MEDIAPLAYER d->videoView = new SlideVideo(this); d->videoView->setInfoInterface(d->settings->iface); d->videoView->installEventFilter(this); connect(d->videoView, SIGNAL(signalVideoLoaded(bool)), this, SLOT(slotVideoLoaded(bool))); connect(d->videoView, SIGNAL(signalVideoFinished()), this, SLOT(slotVideoFinished())); insertWidget(VideoView, d->videoView); #endif // --------------------------------------------------------------- d->endView = new SlideEnd(this); d->endView->installEventFilter(this); insertWidget(EndView, d->endView); // --------------------------------------------------------------- d->osd = new SlideOSD(d->settings, this); d->osd->installEventFilter(this); // --------------------------------------------------------------- d->mouseMoveTimer = new QTimer(this); d->mouseMoveTimer->setSingleShot(true); d->mouseMoveTimer->setInterval(1000); connect(d->mouseMoveTimer, SIGNAL(timeout()), this, SLOT(slotMouseMoveTimeOut())); // --------------------------------------------------------------- QScreen* screen = qApp->primaryScreen(); if (QWidget* const widget = qApp->activeWindow()) { if (QWindow* const window = widget->windowHandle()) { screen = window->screen(); } } const int activeScreenIndex = qMax(qApp->screens().indexOf(screen), 0); const int preferenceScreen = d->settings->slideScreen; int screenIndex = 0; if (preferenceScreen == -2) { screenIndex = activeScreenIndex; } else if (preferenceScreen == -1) { QScreen* const primaryScreen = qApp->primaryScreen(); screenIndex = qApp->screens().indexOf(primaryScreen); } else if ((preferenceScreen >= 0) && (preferenceScreen < qApp->screens().count())) { screenIndex = preferenceScreen; } else { screenIndex = activeScreenIndex; d->settings->slideScreen = -2; d->settings->writeToConfig(); } slotScreenSelected(screenIndex); // --------------------------------------------------------------- inhibitScreenSaver(); slotMouseMoveTimeOut(); setCurrentIndex(ImageView); } SlideShowLoader::~SlideShowLoader() { emit signalLastItemUrl(currentItem()); d->mouseMoveTimer->stop(); allowScreenSaver(); delete d->settings; delete d; } void SlideShowLoader::setCurrentView(SlideShowViewMode view) { switch (view) { case ErrorView: { d->osd->video(false); d->errorView->setCurrentUrl(currentItem()); setCurrentIndex(view); d->osd->setCurrentUrl(currentItem()); break; } case ImageView: { #ifdef HAVE_MEDIAPLAYER d->videoView->stop(); d->osd->video(false); #endif setCurrentIndex(view); d->osd->setCurrentUrl(currentItem()); break; } case VideoView: { #ifdef HAVE_MEDIAPLAYER d->osd->video(true); d->osd->pause(false); setCurrentIndex(view); d->osd->setCurrentUrl(currentItem()); #endif break; } default : // EndView { #ifdef HAVE_MEDIAPLAYER d->videoView->stop(); d->osd->video(false); #endif d->osd->pause(true); setCurrentIndex(view); break; } } } void SlideShowLoader::setCurrentItem(const QUrl& url) { int index = d->settings->indexOf(url); if (index != -1) { d->fileIndex = index - 1; } } QUrl SlideShowLoader::currentItem() const { return d->settings->fileList.value(d->fileIndex); } void SlideShowLoader::setShortCutPrefixes(const QMap& prefixes) { d->shortcutPrefixes = prefixes; } void SlideShowLoader::slotLoadNextItem() { int num = d->settings->count(); if (d->fileIndex == (num - 1)) { if (d->settings->loop) { d->fileIndex = -1; } } d->fileIndex++; qCDebug(DIGIKAM_GENERAL_LOG) << "fileIndex: " << d->fileIndex; if (!d->settings->loop) { d->osd->toolBar()->setEnabledPrev(d->fileIndex > 0); d->osd->toolBar()->setEnabledNext(d->fileIndex < (num - 1)); } if ((d->fileIndex >= 0) && (d->fileIndex < num)) { #ifdef HAVE_MEDIAPLAYER QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(currentItem().toLocalFile()).name().startsWith(QLatin1String("video/"))) { d->videoView->setCurrentUrl(currentItem()); return; } #endif d->imageView->setLoadUrl(currentItem()); } else { endOfSlide(); } } void SlideShowLoader::slotLoadPrevItem() { int num = d->settings->count(); if (d->fileIndex == 0) { if (d->settings->loop) { d->fileIndex = num; } } d->fileIndex--; qCDebug(DIGIKAM_GENERAL_LOG) << "fileIndex: " << d->fileIndex; if (!d->settings->loop) { d->osd->toolBar()->setEnabledPrev(d->fileIndex > 0); d->osd->toolBar()->setEnabledNext(d->fileIndex < (num - 1)); } if ((d->fileIndex >= 0) && (d->fileIndex < num)) { #ifdef HAVE_MEDIAPLAYER QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(currentItem().toLocalFile()) .name().startsWith(QLatin1String("video/"))) { d->videoView->setCurrentUrl(currentItem()); return; } #endif d->imageView->setLoadUrl(currentItem()); } else { endOfSlide(); } } void SlideShowLoader::slotImageLoaded(bool loaded) { if (loaded) { setCurrentView(ImageView); if (d->fileIndex != -1) { if (!d->osd->isPaused()) { d->osd->pause(false); } preloadNextItem(); } } else { #ifdef HAVE_MEDIAPLAYER // Try to load only GIF Images QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(currentItem().toLocalFile()) .name() == QLatin1String("image/gif")) { d->videoView->setCurrentUrl(currentItem()); } #else preloadNextItem(); #endif } d->osd->setLoadingReady(true); } void SlideShowLoader::slotVideoLoaded(bool loaded) { if (loaded) { setCurrentView(VideoView); } else { // Failed to load item setCurrentView(ErrorView); if (d->fileIndex != -1) { if (!d->osd->isPaused()) { d->osd->pause(false); } } } preloadNextItem(); } void SlideShowLoader::slotVideoFinished() { if (d->fileIndex != -1) { d->osd->video(false); slotLoadNextItem(); } } void SlideShowLoader::endOfSlide() { setCurrentView(EndView); d->fileIndex = -1; d->osd->toolBar()->setEnabledPlay(false); d->osd->toolBar()->setEnabledNext(false); d->osd->toolBar()->setEnabledPrev(false); } void SlideShowLoader::preloadNextItem() { int index = d->fileIndex + 1; int num = d->settings->count(); if (index >= num) { if (d->settings->loop) { index = 0; } } if (index < num) { QUrl nextItem = d->settings->fileList.value(index); #ifdef HAVE_MEDIAPLAYER QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(nextItem.toLocalFile()) .name().startsWith(QLatin1String("video/"))) { return; } #endif d->imageView->setPreloadUrl(nextItem); } } void SlideShowLoader::wheelEvent(QWheelEvent* e) { d->osd->toolBar()->closeConfigurationDialog(); if (e->angleDelta().y() < 0) { d->osd->pause(true); slotLoadNextItem(); } if (e->angleDelta().y() > 0) { if (d->fileIndex == -1) { // EndView => backward. d->fileIndex = d->settings->count(); } d->osd->pause(true); slotLoadPrevItem(); } } void SlideShowLoader::mousePressEvent(QMouseEvent* e) { d->osd->toolBar()->closeConfigurationDialog(); - if (d->fileIndex == -1) + if (e->button() == Qt::LeftButton) { - // EndView => close Slideshow view. + if (d->fileIndex == -1) + { + // EndView => close Slideshow view. - close(); + close(); - return; - } + return; + } - if (e->button() == Qt::LeftButton) - { d->osd->pause(true); slotLoadNextItem(); } else if (e->button() == Qt::RightButton) { if (d->fileIndex == -1) { // EndView => backward. - d->fileIndex = d->settings->count() - 1; + d->fileIndex = d->settings->count(); } d->osd->pause(true); slotLoadPrevItem(); } } void SlideShowLoader::keyPressEvent(QKeyEvent* e) { if (!e) { return; } if (e->key() == Qt::Key_F4) { d->osd->toggleProperties(); return; } d->osd->toolBar()->keyPressEvent(e); } bool SlideShowLoader::eventFilter(QObject* obj, QEvent* ev) { if (ev->type() == QEvent::MouseMove) { setCursor(QCursor(Qt::ArrowCursor)); #ifdef HAVE_MEDIAPLAYER d->videoView->showIndicator(true); #endif d->mouseMoveTimer->start(); return false; } // pass the event on to the parent class return QWidget::eventFilter(obj, ev); } void SlideShowLoader::slotMouseMoveTimeOut() { if (!d->osd->isUnderMouse()) { setCursor(QCursor(Qt::BlankCursor)); } #ifdef HAVE_MEDIAPLAYER d->videoView->showIndicator(false); #endif } /** * Inspired from Okular's presentation widget * TODO: Add OSX and Windows support */ void SlideShowLoader::inhibitScreenSaver() { #ifdef HAVE_DBUS QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.ScreenSaver"), QLatin1String("/ScreenSaver"), QLatin1String("org.freedesktop.ScreenSaver"), QLatin1String("Inhibit")); message << QLatin1String("digiKam"); message << i18nc("Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a slideshow"); QDBusReply reply = QDBusConnection::sessionBus().call(message); if (reply.isValid()) { d->screenSaverCookie = reply.value(); } #endif } void SlideShowLoader::allowScreenSaver() { #ifdef HAVE_DBUS if (d->screenSaverCookie != -1) { QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.ScreenSaver"), QLatin1String("/ScreenSaver"), QLatin1String("org.freedesktop.ScreenSaver"), QLatin1String("UnInhibit")); message << (uint)d->screenSaverCookie; QDBusConnection::sessionBus().send(message); } #endif } void SlideShowLoader::slotAssignRating(int rating) { DInfoInterface::DInfoMap info; info.insert(QLatin1String("rating"), rating); d->settings->iface->setItemInfo(currentItem(), info); dispatchCurrentInfoChange(currentItem()); } void SlideShowLoader::slotAssignColorLabel(int color) { DInfoInterface::DInfoMap info; info.insert(QLatin1String("colorlabel"), color); d->settings->iface->setItemInfo(currentItem(), info); dispatchCurrentInfoChange(currentItem()); } void SlideShowLoader::slotAssignPickLabel(int pick) { DInfoInterface::DInfoMap info; info.insert(QLatin1String("picklabel"), pick); d->settings->iface->setItemInfo(currentItem(), info); dispatchCurrentInfoChange(currentItem()); } void SlideShowLoader::slotToggleTag(int tag) { DInfoInterface::DInfoMap info; info.insert(QLatin1String("tag"), tag); d->settings->iface->setItemInfo(currentItem(), info); dispatchCurrentInfoChange(currentItem()); } void SlideShowLoader::slotHandleShortcut(const QString& shortcut, int val) { //qCDebug(DIGIKAM_GENERAL_LOG) << "SlideShowLoader::slotHandleShortcut"; if (d->shortcutPrefixes.contains(QLatin1String("rating")) && shortcut.startsWith(d->shortcutPrefixes[QLatin1String("rating")])) { slotAssignRating(val); return; } if (d->shortcutPrefixes.contains(QLatin1String("colorlabel")) && shortcut.startsWith(d->shortcutPrefixes[QLatin1String("colorlabel")])) { slotAssignColorLabel(val); return; } if (d->shortcutPrefixes.contains(QLatin1String("picklabel")) && shortcut.startsWith(d->shortcutPrefixes[QLatin1String("picklabel")])) { slotAssignPickLabel(val); return; } if (d->shortcutPrefixes.contains(QLatin1String("tag")) && shortcut.startsWith(d->shortcutPrefixes[QLatin1String("tag")])) { slotToggleTag(val); return; } qCWarning(DIGIKAM_GENERAL_LOG) << "Shortcut is not yet supported in SlideShowLoader::slotHandleShortcut():" << shortcut; } void SlideShowLoader::dispatchCurrentInfoChange(const QUrl& url) { if (currentItem() == url) { d->osd->setCurrentUrl(currentItem()); } } void SlideShowLoader::slotPause() { #ifdef HAVE_MEDIAPLAYER if (currentIndex() == VideoView) { d->videoView->pause(true); } else #endif { d->osd->pause(true); } } void SlideShowLoader::slotPlay() { d->settings->suffleImages(); #ifdef HAVE_MEDIAPLAYER if (currentIndex() == VideoView) { d->videoView->pause(false); } else #endif { d->osd->pause(false); } } void SlideShowLoader::slotScreenSelected(int screen) { if (screen >= qApp->screens().count()) { return; } QRect deskRect = qApp->screens().at(screen)->geometry(); setWindowState(windowState() & ~Qt::WindowFullScreen); move(deskRect.x(), deskRect.y()); resize(deskRect.width(), deskRect.height()); setWindowState(windowState() | Qt::WindowFullScreen); // update OSD position if (d->fileIndex != -1) { qApp->processEvents(); d->osd->setCurrentUrl(currentItem()); } qCDebug(DIGIKAM_GENERAL_LOG) << "Slideshow: move to screen: " << screen << " :: " << deskRect; } } // namespace DigikamGenericSlideShowPlugin diff --git a/core/libs/database/utils/ifaces/dio.cpp b/core/libs/database/utils/ifaces/dio.cpp index 0419403a3b..2dd0b1ada5 100644 --- a/core/libs/database/utils/ifaces/dio.cpp +++ b/core/libs/database/utils/ifaces/dio.cpp @@ -1,642 +1,642 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-17 * Description : low level files management interface. * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2012-2013 by Marcel Wiesweg * Copyright (C) 2015 by Mohamed_Anwer * Copyright (C) 2018 by Maik Qualmann * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dio.h" // Qt includes #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "iteminfo.h" #include "diofinders.h" #include "albummanager.h" #include "tagscache.h" #include "coredb.h" #include "coredbaccess.h" #include "album.h" #include "dmetadata.h" #include "metaenginesettings.h" #include "scancontroller.h" #include "thumbsdb.h" #include "thumbsdbaccess.h" #include "iojobsmanager.h" #include "collectionmanager.h" #include "dnotificationwrapper.h" #include "loadingcacheinterface.h" #include "progressmanager.h" #include "digikamapp.h" #include "iojobdata.h" namespace Digikam { class Q_DECL_HIDDEN DIOCreator { public: DIO object; }; Q_GLOBAL_STATIC(DIOCreator, creator) // ------------------------------------------------------------------------------------------------ DIO* DIO::instance() { return &creator->object; } DIO::DIO() { m_processingCount = 0; } DIO::~DIO() { } void DIO::cleanUp() { } bool DIO::itemsUnderProcessing() { return instance()->m_processingCount; } // Album -> Album ----------------------------------------------------- void DIO::copy(PAlbum* const src, PAlbum* const dest) { if (!src || !dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyAlbum, src, dest)); } void DIO::move(PAlbum* const src, PAlbum* const dest) { if (!src || !dest) { return; } #ifdef Q_OS_WIN AlbumManager::instance()->removeWatchedPAlbums(src); #endif instance()->processJob(new IOJobData(IOJobData::MoveAlbum, src, dest)); } // Images -> Album ---------------------------------------------------- void DIO::copy(const QList& infos, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyImage, infos, dest)); } void DIO::move(const QList& infos, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::MoveImage, infos, dest)); } // External files -> album -------------------------------------------- void DIO::copy(const QUrl& src, PAlbum* const dest) { copy(QList() << src, dest); } void DIO::copy(const QList& srcList, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyFiles, srcList, dest)); } void DIO::move(const QUrl& src, PAlbum* const dest) { move(QList() << src, dest); } void DIO::move(const QList& srcList, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::MoveFiles, srcList, dest)); } // Rename -------------------------------------------------------------- void DIO::rename(const QUrl& src, const QString& newName, bool overwrite) { if (src.isEmpty() || newName.isEmpty()) { return; } ItemInfo info = ItemInfo::fromUrl(src); instance()->processJob(new IOJobData(IOJobData::Rename, info, newName, overwrite)); } // Delete -------------------------------------------------------------- void DIO::del(const QList& infos, bool useTrash) { instance()->processJob(new IOJobData(useTrash ? IOJobData::Trash : IOJobData::Delete, infos)); } void DIO::del(const ItemInfo& info, bool useTrash) { del(QList() << info, useTrash); } void DIO::del(PAlbum* const album, bool useTrash) { if (!album) { return; } #ifdef Q_OS_WIN AlbumManager::instance()->removeWatchedPAlbums(album); #endif instance()->createJob(new IOJobData(useTrash ? IOJobData::Trash : IOJobData::Delete, album)); } // Restore Trash ------------------------------------------------------- void DIO::restoreTrash(const DTrashItemInfoList& infos) { instance()->createJob(new IOJobData(IOJobData::Restore, infos)); } // Empty Trash --------------------------------------------------------- void DIO::emptyTrash(const DTrashItemInfoList& infos) { instance()->createJob(new IOJobData(IOJobData::Empty, infos)); } // ------------------------------------------------------------------------------------------------ void DIO::processJob(IOJobData* const data) { const int operation = data->operation(); if (operation == IOJobData::CopyImage || operation == IOJobData::MoveImage) { // this is a fast db operation, do here GroupedImagesFinder finder(data->itemInfos()); data->setItemInfos(finder.infos); QStringList filenames; QList ids; foreach (const ItemInfo& info, data->itemInfos()) { filenames << info.name(); ids << info.id(); } ScanController::instance()->hintAtMoveOrCopyOfItems(ids, data->destAlbum(), filenames); } else if (operation == IOJobData::CopyAlbum || operation == IOJobData::MoveAlbum) { ScanController::instance()->hintAtMoveOrCopyOfAlbum(data->srcAlbum(), data->destAlbum()); createJob(data); return; } else if (operation == IOJobData::Delete || operation == IOJobData::Trash) { qCDebug(DIGIKAM_DATABASE_LOG) << "Number of files to be deleted:" << data->sourceUrls().count(); } SidecarFinder finder(data->sourceUrls()); data->setSourceUrls(finder.localFiles); if (operation == IOJobData::Rename) { if (!data->itemInfos().isEmpty()) { ItemInfo info = data->itemInfos().first(); PAlbum* const album = AlbumManager::instance()->findPAlbum(info.albumId()); if (album) { ScanController::instance()->hintAtMoveOrCopyOfItem(info.id(), album, data->destUrl().fileName()); } for (int i = 0 ; i < finder.localFiles.length() ; ++i) { if (finder.localFileModes.at(i)) { data->setDestUrl(finder.localFiles.at(i), QUrl::fromLocalFile(data->destUrl().toLocalFile() + finder.localFileSuffixes.at(i))); } else { - QFileInfo info(data->destUrl().toLocalFile()); + QFileInfo basInfo(data->destUrl().toLocalFile()); data->setDestUrl(finder.localFiles.at(i), - QUrl::fromLocalFile(info.path() + - QLatin1Char('/') + - info.completeBaseName() + + QUrl::fromLocalFile(basInfo.path() + + QLatin1Char('/') + + basInfo.completeBaseName() + finder.localFileSuffixes.at(i))); } } } } createJob(data); } void DIO::createJob(IOJobData* const data) { if (data->sourceUrls().isEmpty()) { delete data; return; } ProgressItem* item = nullptr; QString itemString = getItemString(data); if (!itemString.isEmpty()) { item = ProgressManager::instance()->createProgressItem(itemString, QString(), true, false); item->setTotalItems(data->sourceUrls().count()); data->setProgressId(item->id()); } IOJobsThread* const jobThread = IOJobsManager::instance()->startIOJobs(data); connect(jobThread, SIGNAL(signalOneProccessed(QUrl)), this, SLOT(slotOneProccessed(QUrl))); connect(jobThread, SIGNAL(finished()), this, SLOT(slotResult())); if (data->operation() == IOJobData::Rename) { connect(jobThread, SIGNAL(signalRenameFailed(QUrl)), this, SIGNAL(signalRenameFailed(QUrl))); connect(jobThread, SIGNAL(finished()), this, SIGNAL(signalRenameFinished())); } if (data->operation() == IOJobData::Empty || data->operation() == IOJobData::Restore) { connect(jobThread, SIGNAL(finished()), this, SIGNAL(signalTrashFinished())); } if (item) { connect(item, SIGNAL(progressItemCanceled(ProgressItem*)), jobThread, SLOT(slotCancel())); connect(item, SIGNAL(progressItemCanceled(ProgressItem*)), this, SLOT(slotCancel(ProgressItem*))); } ++m_processingCount; } void DIO::slotResult() { IOJobsThread* const jobThread = dynamic_cast(sender()); if (!jobThread || !jobThread->jobData()) { return; } IOJobData* const data = jobThread->jobData(); if (jobThread->hasErrors() && data->operation() != IOJobData::Rename) { // Pop-up a message about the error. QString errors = jobThread->errorsList().join(QLatin1Char('\n')); DNotificationWrapper(QString(), errors, DigikamApp::instance(), DigikamApp::instance()->windowTitle()); } if (m_processingCount) { --m_processingCount; } slotCancel(getProgressItem(data)); } void DIO::slotOneProccessed(const QUrl& url) { IOJobsThread* const jobThread = dynamic_cast(sender()); if (!jobThread || !jobThread->jobData()) { return; } IOJobData* const data = jobThread->jobData(); const int operation = data->operation(); if (operation == IOJobData::MoveImage) { ItemInfo info = data->findItemInfo(url); if (!info.isNull() && data->destAlbum()) { CoreDbAccess().db()->moveItem(info.albumId(), info.name(), data->destAlbum()->id(), info.name()); } } else if (operation == IOJobData::Delete) { // Mark the images as obsolete and remove them // from their album and from the grouped PAlbum* const album = data->srcAlbum(); if (album && album->fileUrl() == url) { // get all deleted albums CoreDbAccess access; QList albumsToDelete; QList imagesToRemove; addAlbumChildrenToList(albumsToDelete, album); foreach (int albumId, albumsToDelete) { imagesToRemove << access.db()->getItemIDsInAlbum(albumId); } foreach (const qlonglong& imageId, imagesToRemove) { access.db()->removeAllImageRelationsFrom(imageId, DatabaseRelation::Grouped); } access.db()->removeItemsPermanently(imagesToRemove, albumsToDelete); } else { ItemInfo info = data->findItemInfo(url); if (!info.isNull()) { int originalVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph()); QList imageIds = CoreDbAccess().db()->getImagesRelatedFrom(info.id(), DatabaseRelation::DerivedFrom); CoreDbAccess access; foreach (const qlonglong& id, imageIds) { access.db()->removeItemTag(id, originalVersionTag); access.db()->addItemTag(id, needTaggingTag); } access.db()->removeAllImageRelationsFrom(info.id(), DatabaseRelation::Grouped); access.db()->removeItemsPermanently(QList() << info.id(), QList() << info.albumId()); } } } else if (operation == IOJobData::Trash) { ItemInfo info = data->findItemInfo(url); if (!info.isNull()) { int originalVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph()); QList imageIds = CoreDbAccess().db()->getImagesRelatedFrom(info.id(), DatabaseRelation::DerivedFrom); CoreDbAccess access; foreach (const qlonglong& id, imageIds) { access.db()->removeItemTag(id, originalVersionTag); access.db()->addItemTag(id, needTaggingTag); } access.db()->removeItems(QList() << info.id(), QList() << info.albumId()); } } else if (operation == IOJobData::Rename) { ItemInfo info = data->findItemInfo(url); if (!info.isNull()) { QString oldPath = url.toLocalFile(); QString newName = data->destUrl(url).fileName(); QString newPath = data->destUrl(url).toLocalFile(); if (data->overwrite()) { ThumbsDbAccess().db()->removeByFilePath(newPath); LoadingCacheInterface::fileChanged(newPath, false); CoreDbAccess().db()->deleteItem(info.albumId(), newName); } ThumbsDbAccess().db()->renameByFilePath(oldPath, newPath); // Remove old thumbnails and images from the cache LoadingCacheInterface::fileChanged(oldPath, false); // Rename in ItemInfo and database info.setName(newName); } } // Scan folders for changes if (operation == IOJobData::Delete || operation == IOJobData::Trash || operation == IOJobData::MoveAlbum) { PAlbum* const album = data->srcAlbum(); QString scanPath; if (album) { PAlbum* const parent = dynamic_cast(album->parent()); if (parent) { scanPath = parent->fileUrl().toLocalFile(); } } if (scanPath.isEmpty()) { scanPath = url.adjusted(QUrl::RemoveFilename).toLocalFile(); } ScanController::instance()->scheduleCollectionScanRelaxed(scanPath); } if (operation == IOJobData::CopyImage || operation == IOJobData::CopyAlbum || operation == IOJobData::CopyFiles || operation == IOJobData::MoveImage || operation == IOJobData::MoveAlbum || operation == IOJobData::MoveFiles) { QString scanPath = data->destUrl().toLocalFile(); ScanController::instance()->scheduleCollectionScanRelaxed(scanPath); } if (operation == IOJobData::Restore) { QString scanPath = url.adjusted(QUrl::RemoveFilename).toLocalFile(); ScanController::instance()->scheduleCollectionScanRelaxed(scanPath); } ProgressItem* const item = getProgressItem(data); if (item) { item->advance(1); } } QString DIO::getItemString(IOJobData* const data) const { switch (data->operation()) { case IOJobData::CopyAlbum: return i18n("Copy Album"); case IOJobData::CopyImage: return i18n("Copy Images"); case IOJobData::CopyFiles: return i18n("Copy Files"); case IOJobData::MoveAlbum: return i18n("Move Album"); case IOJobData::MoveImage: return i18n("Move Images"); case IOJobData::MoveFiles: return i18n("Move Files"); case IOJobData::Delete: return i18n("Delete"); case IOJobData::Trash: return i18n("Trash"); case IOJobData::Restore: return i18n("Restore Trash"); case IOJobData::Empty: return i18n("Empty Trash"); default: break; } return QString(); } ProgressItem* DIO::getProgressItem(IOJobData* const data) const { QString itemId = data->getProgressId(); if (itemId.isEmpty()) { return nullptr; } return ProgressManager::instance()->findItembyId(itemId); } void DIO::slotCancel(ProgressItem* item) { if (item) { item->setComplete(); } } void DIO::addAlbumChildrenToList(QList& list, Album* const album) { // simple recursive helper function if (album) { if (!list.contains(album->id())) { list.append(album->id()); } AlbumIterator it(album); while (it.current()) { addAlbumChildrenToList(list, *it); ++it; } } } } // namespace Digikam diff --git a/core/libs/database/utils/widgets/dbsettingswidget.cpp b/core/libs/database/utils/widgets/dbsettingswidget.cpp index 984a7f52f1..6d652d3927 100644 --- a/core/libs/database/utils/widgets/dbsettingswidget.cpp +++ b/core/libs/database/utils/widgets/dbsettingswidget.cpp @@ -1,855 +1,860 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-11-14 * Description : database settings widget * * Copyright (C) 2009-2010 by Holger Foerster * Copyright (C) 2010-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dbsettingswidget_p.h" namespace Digikam { DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* const parent) : QWidget(parent), d(new Private) { setupMainArea(); } DatabaseSettingsWidget::~DatabaseSettingsWidget() { delete d; } void DatabaseSettingsWidget::setupMainArea() { QVBoxLayout* const layout = new QVBoxLayout(); setLayout(layout); // -------------------------------------------------------- const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QGroupBox* const dbConfigBox = new QGroupBox(i18n("Database Configuration"), this); QVBoxLayout* const vlay = new QVBoxLayout(dbConfigBox); DHBox* const typeHbox = new DHBox(); QLabel* const databaseTypeLabel = new QLabel(typeHbox); d->dbType = new QComboBox(typeHbox); databaseTypeLabel->setText(i18n("Type:")); // --------- fill with default values --------------------- int dbTypeIdx = 0; d->dbType->addItem(i18n("SQLite"), SQlite); d->dbTypeMap[SQlite] = dbTypeIdx++; #ifdef HAVE_MYSQLSUPPORT # ifdef HAVE_INTERNALMYSQL d->dbType->addItem(i18n("Mysql Internal (experimental)"), MysqlInternal); d->dbTypeMap[MysqlInternal] = dbTypeIdx++; # endif d->dbType->addItem(i18n("MySQL Server (experimental)"), MysqlServer); d->dbTypeMap[MysqlServer] = dbTypeIdx++; #endif QString tip = i18n("

Select here the type of database backend.

" "

SQlite backend is for local database storage with a small or medium collection sizes. " "It is the default and recommended backend for collections with less than 100K items.

"); #ifdef HAVE_MYSQLSUPPORT # ifdef HAVE_INTERNALMYSQL tip.append(i18n("

MySQL Internal backend is for local database storage with huge collection sizes. " "This backend is recommend for local collections with more than 100K items.

" "

Be careful: this one still in experimental stage.

")); # endif tip.append(i18n("

MySQL Server backend is a more robust solution especially for remote and shared database storage. " "It is also more efficient to manage huge collection sizes with more than 100K items.

" "

Be careful: this one still in experimental stage.

")); #endif d->dbType->setToolTip(tip); // -------------------------------------------------------- d->dbPathLabel = new QLabel(i18n("

Set here the location where the database files will be stored on your system. " "There are three databases: one for all collections properties, " "one to store compressed thumbnails, " "and one to store faces recognition metadata.
" "Write access is required to be able to edit image properties.

" "

Databases are digiKam core engines. Take care to use a place hosted by fast " "hardware (as SSD) with enough free space especially for thumbnails database.

" "

Note: a remote file system such as NFS, cannot be used here. " "For performance reasons, it is also recommended not to use removable media.

" "

"), dbConfigBox); d->dbPathLabel->setWordWrap(true); d->dbPathEdit = new DFileSelector(dbConfigBox); d->dbPathEdit->setFileDlgMode(QFileDialog::Directory); // -------------------------------------------------------- d->mysqlCmdBox = new DVBox(dbConfigBox); d->mysqlCmdBox->layout()->setContentsMargins(0, 0, 0, 0); new DLineWidget(Qt::Horizontal, d->mysqlCmdBox); QLabel* const mysqlBinariesLabel = new QLabel(i18n("

Here you can configure locations where MySQL binary tools are located. " "digiKam will try to find these binaries automatically if they are " "already installed on your computer.

"), d->mysqlCmdBox); mysqlBinariesLabel->setWordWrap(true); QGroupBox* const binaryBox = new QGroupBox(d->mysqlCmdBox); QGridLayout* const binaryLayout = new QGridLayout; binaryBox->setLayout(binaryLayout); binaryBox->setTitle(i18nc("@title:group", "MySQL Binaries")); d->dbBinariesWidget = new DBinarySearch(binaryBox); d->dbBinariesWidget->header()->setSectionHidden(2, true); d->dbBinariesWidget->addBinary(d->mysqlInitBin); d->dbBinariesWidget->addBinary(d->mysqlServBin); #ifdef Q_OS_LINUX d->dbBinariesWidget->addDirectory(QLatin1String("/usr/bin")); d->dbBinariesWidget->addDirectory(QLatin1String("/usr/sbin")); #endif #ifdef Q_OS_OSX // Std Macports install d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/bin")); d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/sbin")); d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/lib/mariadb/bin")); // digiKam Bundle PKG install d->dbBinariesWidget->addDirectory(QLatin1String("/opt/digikam/bin")); d->dbBinariesWidget->addDirectory(QLatin1String("/opt/digikam/sbin")); d->dbBinariesWidget->addDirectory(QLatin1String("/opt/digikam/lib/mariadb/bin")); #endif #ifdef Q_OS_WIN d->dbBinariesWidget->addDirectory(QLatin1String("C:/Program Files/MariaDB 10.3/bin")); d->dbBinariesWidget->addDirectory(QLatin1String("C:/Program Files (x86/MariaDB 10.3/bin")); #endif d->dbBinariesWidget->allBinariesFound(); // -------------------------------------------------------- d->tab = new QTabWidget(this); QLabel* const hostNameLabel = new QLabel(i18n("Host Name:")); d->hostName = new QLineEdit(); d->hostName->setPlaceholderText(i18n("Set the host computer name")); d->hostName->setToolTip(i18n("This is the computer name running MySQL server.\nThis can be \"localhost\" for a local server, " "or the network computer\n name (or IP address) in case of remote computer.")); QLabel* const connectOptsLabel = new QLabel(i18n("Connect options:")); connectOptsLabel->setOpenExternalLinks(true); d->connectOpts = new QLineEdit(); d->connectOpts->setPlaceholderText(i18n("Set the database connection options")); d->connectOpts->setToolTip(i18n("Set the MySQL server connection options.\nFor advanced users only.")); QLabel* const userNameLabel = new QLabel(i18n("User:")); d->userName = new QLineEdit(); d->userName->setPlaceholderText(i18n("Set the database account name")); d->userName->setToolTip(i18n("Set the MySQL server account name used\nby digiKam to be connected to the server.\n" "This account must be available on the remote MySQL server when database have been created.")); QLabel* const passwordLabel = new QLabel(i18n("Password:")); d->password = new QLineEdit(); d->password->setToolTip(i18n("Set the MySQL server account password used\nby digiKam to be connected to the server.\n" "You can left this field empty to use an account set without password.")); d->password->setEchoMode(QLineEdit::Password); DHBox* const phbox = new DHBox(); QLabel* const hostPortLabel = new QLabel(i18n("Host Port:")); d->hostPort = new QSpinBox(phbox); d->hostPort->setToolTip(i18n("Set the host computer port.\nUsually, MySQL server use port number 3306 by default")); d->hostPort->setMaximum(65535); d->hostPort->setValue(3306); QWidget* const space = new QWidget(phbox); phbox->setStretchFactor(space, 10); QPushButton* const checkDBConnectBtn = new QPushButton(i18n("Check Connection"), phbox); checkDBConnectBtn->setToolTip(i18n("Run a basic database connection to see if current MySQL server settings is suitable.")); // Only accept printable Ascii char for database names. QRegExp asciiRx(QLatin1String("[\x20-\x7F]+$")); QValidator* const asciiValidator = new QRegExpValidator(asciiRx, this); QLabel* const dbNameCoreLabel = new QLabel(i18n("Core Db Name:")); d->dbNameCore = new QLineEdit(); d->dbNameCore->setPlaceholderText(i18n("Set the core database name")); d->dbNameCore->setToolTip(i18n("The core database is lead digiKam container used to store\nalbums, items, and searches metadata.")); d->dbNameCore->setValidator(asciiValidator); QLabel* const dbNameThumbsLabel = new QLabel(i18n("Thumbs Db Name:")); d->dbNameThumbs = new QLineEdit(); d->dbNameThumbs->setPlaceholderText(i18n("Set the thumbnails database name")); d->dbNameThumbs->setToolTip(i18n("The thumbnails database is used by digiKam to host\nimage thumbs with wavelets compression images.\n" "This one can use quickly a lots of space,\nespecially if you have huge collections.")); d->dbNameThumbs->setValidator(asciiValidator); QLabel* const dbNameFaceLabel = new QLabel(i18n("Face Db Name:")); d->dbNameFace = new QLineEdit(); d->dbNameFace->setPlaceholderText(i18n("Set the face database name")); d->dbNameFace->setToolTip(i18n("The face database is used by digiKam to host image histograms\ndedicated to faces recognition process.\n" "This one can use quickly a lots of space, especially\nif you a lots of image with people faces detected " "and tagged.")); d->dbNameFace->setValidator(asciiValidator); QLabel* const dbNameSimilarityLabel = new QLabel(i18n("Similarity Db Name:")); d->dbNameSimilarity = new QLineEdit(); d->dbNameSimilarity->setPlaceholderText(i18n("Set the similarity database name")); d->dbNameSimilarity->setToolTip(i18n("The similarity database is used by digiKam to host image Haar matrix data for the similarity search.")); d->dbNameSimilarity->setValidator(asciiValidator); QPushButton* const defaultValuesBtn = new QPushButton(i18n("Default Settings")); defaultValuesBtn->setToolTip(i18n("Reset database names settings to common default values.")); d->expertSettings = new QGroupBox(); d->expertSettings->setFlat(true); QFormLayout* const expertSettinglayout = new QFormLayout(); d->expertSettings->setLayout(expertSettinglayout); expertSettinglayout->addRow(hostNameLabel, d->hostName); expertSettinglayout->addRow(userNameLabel, d->userName); expertSettinglayout->addRow(passwordLabel, d->password); expertSettinglayout->addRow(connectOptsLabel, d->connectOpts); expertSettinglayout->addRow(hostPortLabel, phbox); expertSettinglayout->addRow(new DLineWidget(Qt::Horizontal, d->expertSettings)); expertSettinglayout->addRow(dbNameCoreLabel, d->dbNameCore); expertSettinglayout->addRow(dbNameThumbsLabel, d->dbNameThumbs); expertSettinglayout->addRow(dbNameFaceLabel, d->dbNameFace); expertSettinglayout->addRow(dbNameSimilarityLabel, d->dbNameSimilarity); expertSettinglayout->addRow(new QWidget(), defaultValuesBtn); d->tab->addTab(d->expertSettings, i18n("Remote Server Settings")); // -------------------------------------------------------- d->dbNoticeBox = new QGroupBox(i18n("Database Server Instructions"), this); QVBoxLayout* const vlay2 = new QVBoxLayout(d->dbNoticeBox); QLabel* const notice = new QLabel(i18n("

digiKam expects that database is already created with a dedicated user account. " "This user name digikam will require full access to the database.
" "If your database is not already set up, you can use the following SQL commands " "(after replacing the password with the correct one).

"), d->dbNoticeBox); notice->setWordWrap(true); d->sqlInit = new QTextBrowser(d->dbNoticeBox); d->sqlInit->setOpenExternalLinks(false); d->sqlInit->setOpenLinks(false); d->sqlInit->setReadOnly(false); QLabel* const notice2 = new QLabel(i18n("

Note: with a Linux server, a database can be initialized following the commands below:

" "

# su

" "

# systemctl restart mysqld

" "

# mysql -u root

" "

...

" "

Enter SQL code to Mysql prompt in order to init digiKam databases with grant privileges (see behind)

" "

...

" "

quit

" "

NOTE: If you have an enormous collection, you should start MySQL server with " "mysql --max_allowed_packet=128M OR in my.ini or ~/.my.cnf, change the settings

"), d->dbNoticeBox); notice2->setWordWrap(true); notice2->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); vlay2->addWidget(notice); vlay2->addWidget(d->sqlInit); vlay2->addWidget(notice2); vlay2->setContentsMargins(spacing, spacing, spacing, spacing); vlay2->setSpacing(spacing); d->tab->addTab(d->dbNoticeBox, i18n("Requirements")); // -------------------------------------------------------- d->dbDetailsBox = new QGroupBox(i18n("Database Server Technical Details"), this); QVBoxLayout* const vlay3 = new QVBoxLayout(d->dbDetailsBox); QLabel* const details = new QLabel(i18n("

Use this configuration view to set all information " "to be connected to a remote " "Mysql database server " "(or MariaDB) " "through a network. " "As with Sqlite or MySQL internal server, 3 databases will be stored " "on the remote server: one for all collections properties, " "one to store compressed thumbnails, and one to store faces " "recognition metadata.

" "

Unlike Sqlite or MySQL internal server, you can customize the " "database names to simplify your backups.

" "

Databases are digiKam core engines. To prevent performance issues, " "take a care to use a fast network link between the client and the server " "computers. It is also recommended to host database files on " "fast hardware (as SSD) " "with enough free space, especially for thumbnails database, even if data are compressed using wavelets image format " "PGF.

" "

The databases must be created previously on the remote server by the administrator. " "Look in Requirements tab for details.

"), d->dbDetailsBox); details->setWordWrap(true); details->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); vlay3->addWidget(details); vlay3->setContentsMargins(spacing, spacing, spacing, spacing); vlay3->setSpacing(spacing); d->tab->addTab(d->dbDetailsBox, i18n("Documentation")); // -------------------------------------------------------- vlay->addWidget(typeHbox); vlay->addWidget(new DLineWidget(Qt::Horizontal)); vlay->addWidget(d->dbPathLabel); vlay->addWidget(d->dbPathEdit); vlay->addWidget(d->mysqlCmdBox); vlay->addWidget(d->tab); vlay->setContentsMargins(spacing, spacing, spacing, spacing); vlay->setSpacing(spacing); // -------------------------------------------------------- layout->setContentsMargins(QMargins()); layout->setSpacing(spacing); layout->addWidget(dbConfigBox); layout->addStretch(); // -------------------------------------------------------- connect(d->dbType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotHandleDBTypeIndexChanged(int))); connect(checkDBConnectBtn, SIGNAL(clicked()), this, SLOT(slotCheckMysqlServerConnection())); connect(defaultValuesBtn, SIGNAL(clicked()), this, SLOT(slotResetMysqlServerDBNames())); connect(d->dbNameCore, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); connect(d->dbNameThumbs, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); connect(d->dbNameFace, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); connect(d->dbNameSimilarity, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); connect(d->userName, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSqlInit())); slotHandleDBTypeIndexChanged(d->dbType->currentIndex()); } int DatabaseSettingsWidget::databaseType() const { return d->dbType->currentData().toInt(); } QString DatabaseSettingsWidget::databasePath() const { return d->dbPathEdit->fileDlgPath(); } void DatabaseSettingsWidget::setDatabasePath(const QString& path) { d->dbPathEdit->setFileDlgPath(path); } DbEngineParameters DatabaseSettingsWidget::orgDatabasePrm() const { return d->orgPrms; } QString DatabaseSettingsWidget::databaseBackend() const { switch (databaseType()) { case MysqlInternal: case MysqlServer: { return DbEngineParameters::MySQLDatabaseType(); } default: // SQlite { return DbEngineParameters::SQLiteDatabaseType(); } } } void DatabaseSettingsWidget::slotResetMysqlServerDBNames() { d->dbNameCore->setText(QLatin1String("digikam")); d->dbNameThumbs->setText(QLatin1String("digikam")); d->dbNameFace->setText(QLatin1String("digikam")); d->dbNameSimilarity->setText(QLatin1String("digikam")); } void DatabaseSettingsWidget::slotHandleDBTypeIndexChanged(int index) { int dbType = d->dbType->itemData(index).toInt(); setDatabaseInputFields(dbType); handleInternalServer(dbType); slotUpdateSqlInit(); } void DatabaseSettingsWidget::setDatabaseInputFields(int index) { switch (index) { case SQlite: { d->dbPathLabel->setVisible(true); d->dbPathEdit->setVisible(true); d->mysqlCmdBox->setVisible(false); d->tab->setVisible(false); connect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotDatabasePathEditedDelayed())); break; } case MysqlInternal: { d->dbPathLabel->setVisible(true); d->dbPathEdit->setVisible(true); d->mysqlCmdBox->setVisible(true); d->tab->setVisible(false); connect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotDatabasePathEditedDelayed())); break; } default: // MysqlServer { d->dbPathLabel->setVisible(false); d->dbPathEdit->setVisible(false); d->mysqlCmdBox->setVisible(false); d->tab->setVisible(true); disconnect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotDatabasePathEditedDelayed())); break; } } } void DatabaseSettingsWidget::handleInternalServer(int index) { bool internal = (index == MysqlInternal); d->hostName->setDisabled(internal); d->hostPort->setDisabled(internal); d->dbNameCore->setDisabled(internal); d->dbNameThumbs->setDisabled(internal); d->dbNameFace->setDisabled(internal); d->dbNameSimilarity->setDisabled(internal); d->userName->setDisabled(internal); d->password->setDisabled(internal); d->connectOpts->setDisabled(internal); } void DatabaseSettingsWidget::slotUpdateSqlInit() { QString sql = QString::fromLatin1("CREATE USER \'%1\'@\'%2\' IDENTIFIED BY \'password\';
") .arg(d->userName->text()) .arg(d->hostName->text()); sql += QString::fromLatin1("GRANT ALL ON *.* TO \'%1\'@\'%2\' IDENTIFIED BY \'password\';
") .arg(d->userName->text()) .arg(d->hostName->text()); sql += QString::fromLatin1("CREATE DATABASE %1;
" "GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%4\';
") .arg(d->dbNameCore->text()) .arg(d->dbNameCore->text()) .arg(d->userName->text()) .arg(d->hostName->text()); if (d->dbNameThumbs->text() != d->dbNameCore->text()) { sql += QString::fromLatin1("CREATE DATABASE %1;
" "GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%4\';
") .arg(d->dbNameThumbs->text()) .arg(d->dbNameThumbs->text()) .arg(d->userName->text()) .arg(d->hostName->text()); } if ((d->dbNameFace->text() != d->dbNameCore->text()) && (d->dbNameFace->text() != d->dbNameThumbs->text())) { sql += QString::fromLatin1("CREATE DATABASE %1;
" "GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%4\';
") .arg(d->dbNameFace->text()) .arg(d->dbNameFace->text()) .arg(d->userName->text()) .arg(d->hostName->text()); } if ((d->dbNameSimilarity->text() != d->dbNameCore->text()) && (d->dbNameSimilarity->text() != d->dbNameThumbs->text()) && (d->dbNameSimilarity->text() != d->dbNameFace->text())) { sql += QString::fromLatin1("CREATE DATABASE %1;
" "GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%4\';
") .arg(d->dbNameSimilarity->text()) .arg(d->dbNameSimilarity->text()) .arg(d->userName->text()) .arg(d->hostName->text()); } sql += QLatin1String("FLUSH PRIVILEGES;
"); d->sqlInit->setText(sql); } void DatabaseSettingsWidget::slotCheckMysqlServerConnection() { QString error; if (checkMysqlServerConnection(error)) { QMessageBox::information(qApp->activeWindow(), i18n("Database connection test"), i18n("Database connection test successful.")); } else { QMessageBox::critical(qApp->activeWindow(), i18n("Database connection test"), i18n("Database connection test was not successful.

Error was: %1

", error)); } } bool DatabaseSettingsWidget::checkMysqlServerConnectionConfig(QString& error) { if (d->hostName->text().isEmpty()) { error = i18n("The server hostname is empty"); return false; } if (d->userName->text().isEmpty()) { error = i18n("The server user name is empty"); return false; } return true; } bool DatabaseSettingsWidget::checkMysqlServerDbNamesConfig(QString& error) { if (d->dbNameCore->text().isEmpty()) { error = i18n("The core database name is empty"); return false; } if (d->dbNameThumbs->text().isEmpty()) { error = i18n("The thumbnails database name is empty"); return false; } if (d->dbNameFace->text().isEmpty()) { error = i18n("The face database name is empty"); return false; } if (d->dbNameSimilarity->text().isEmpty()) { error = i18n("The similarity database name is empty"); return false; } return true; } bool DatabaseSettingsWidget::checkMysqlServerConnection(QString& error) { if (!checkMysqlServerConnectionConfig(error)) { return false; } bool result = false; qApp->setOverrideCursor(Qt::WaitCursor); AlbumManager::instance()->addFakeConnection(); QString databaseID(QLatin1String("ConnectionTest")); { QSqlDatabase testDatabase = QSqlDatabase::addDatabase(databaseBackend(), databaseID); DbEngineParameters prm = getDbEngineParameters(); qCDebug(DIGIKAM_DATABASE_LOG) << "Testing DB connection (" << databaseID << ") with these settings:"; qCDebug(DIGIKAM_DATABASE_LOG) << prm; testDatabase.setHostName(prm.hostName); testDatabase.setPort(prm.port); testDatabase.setUserName(prm.userName); testDatabase.setPassword(prm.password); testDatabase.setConnectOptions(prm.connectOptions); result = testDatabase.open(); error = testDatabase.lastError().text(); testDatabase.close(); } QSqlDatabase::removeDatabase(databaseID); qApp->restoreOverrideCursor(); return result; } void DatabaseSettingsWidget::setParametersFromSettings(const ApplicationSettings* const settings, const bool& migration) { d->orgPrms = settings->getDbEngineParameters(); if (d->orgPrms.databaseType == DbEngineParameters::SQLiteDatabaseType()) { d->dbPathEdit->setFileDlgPath(d->orgPrms.getCoreDatabaseNameOrDir()); d->dbType->setCurrentIndex(d->dbTypeMap[SQlite]); slotResetMysqlServerDBNames(); if (settings->getDatabaseDirSetAtCmd() && !migration) { d->dbType->setEnabled(false); d->dbPathEdit->setEnabled(false); d->dbPathLabel->setText(d->dbPathLabel->text() + i18n("This path was set as a command line" "option (--database-directory).")); } } #ifdef HAVE_MYSQLSUPPORT # ifdef HAVE_INTERNALMYSQL else if (d->orgPrms.databaseType == DbEngineParameters::MySQLDatabaseType() && d->orgPrms.internalServer) { d->dbPathEdit->setFileDlgPath(d->orgPrms.internalServerPath()); d->dbType->setCurrentIndex(d->dbTypeMap[MysqlInternal]); d->mysqlInitBin.setup(QFileInfo(d->orgPrms.internalServerMysqlInitCmd).absoluteFilePath()); d->mysqlServBin.setup(QFileInfo(d->orgPrms.internalServerMysqlServCmd).absoluteFilePath()); d->dbBinariesWidget->allBinariesFound(); slotResetMysqlServerDBNames(); } # endif else { d->dbType->setCurrentIndex(d->dbTypeMap[MysqlServer]); d->dbNameCore->setText(d->orgPrms.databaseNameCore); d->dbNameThumbs->setText(d->orgPrms.databaseNameThumbnails); d->dbNameFace->setText(d->orgPrms.databaseNameFace); d->dbNameSimilarity->setText(d->orgPrms.databaseNameSimilarity); d->hostName->setText(d->orgPrms.hostName); d->hostPort->setValue((d->orgPrms.port == -1) ? 3306 : d->orgPrms.port); d->connectOpts->setText(d->orgPrms.connectOptions); d->userName->setText(d->orgPrms.userName); d->password->setText(d->orgPrms.password); } #endif slotHandleDBTypeIndexChanged(d->dbType->currentIndex()); } DbEngineParameters DatabaseSettingsWidget::getDbEngineParameters() const { DbEngineParameters prm; switch(databaseType()) { case SQlite: prm = DbEngineParameters::parametersForSQLiteDefaultFile(databasePath()); break; case MysqlInternal: prm = DbEngineParameters::defaultParameters(databaseBackend()); prm.setInternalServerPath(databasePath()); prm.internalServerMysqlInitCmd = d->mysqlInitBin.path(); prm.internalServerMysqlServCmd = d->mysqlServBin.path(); break; default: // MysqlServer prm.internalServer = false; prm.databaseType = databaseBackend(); prm.databaseNameCore = d->dbNameCore->text(); prm.databaseNameThumbnails = d->dbNameThumbs->text(); prm.databaseNameFace = d->dbNameFace->text(); prm.databaseNameSimilarity = d->dbNameSimilarity->text(); prm.connectOptions = d->connectOpts->text(); prm.hostName = d->hostName->text(); prm.port = d->hostPort->value(); prm.userName = d->userName->text(); prm.password = d->password->text(); break; } return prm; } void DatabaseSettingsWidget::slotDatabasePathEditedDelayed() { QTimer::singleShot(300, this, SLOT(slotDatabasePathEdited())); } void DatabaseSettingsWidget::slotDatabasePathEdited() { QString newPath = databasePath(); #ifndef Q_OS_WIN if (!newPath.isEmpty() && !QDir::isAbsolutePath(newPath)) { d->dbPathEdit->setFileDlgPath(QDir::homePath() + QLatin1Char('/') + newPath); } #endif d->dbPathEdit->setFileDlgPath(newPath); } bool DatabaseSettingsWidget::checkDatabaseSettings() { switch (databaseType()) { case SQlite: { return checkDatabasePath(); } case MysqlInternal: { if (!checkDatabasePath()) return false; if (!d->dbBinariesWidget->allBinariesFound()) return false; return true; } default: // MysqlServer { QString error; if (!checkMysqlServerDbNamesConfig(error)) { QMessageBox::critical(qApp->activeWindow(), i18n("Database Configuration"), i18n("The database names configuration is not valid. Error is

%1


" "Please check your configuration.", error)); return false; } if (!checkMysqlServerConnection(error)) { QMessageBox::critical(qApp->activeWindow(), i18n("Database Connection Test"), i18n("Testing database connection has failed with error

%1


" "Please check your configuration.", error)); return false; } } } return true; } bool DatabaseSettingsWidget::checkDatabasePath() { QString dbFolder = databasePath(); qCDebug(DIGIKAM_DATABASE_LOG) << "Database directory is : " << dbFolder; if (dbFolder.isEmpty()) { QMessageBox::information(qApp->activeWindow(), qApp->applicationName(), i18n("You must select a folder for digiKam to " "store information and metadata in a database file.")); return false; } QDir targetPath(dbFolder); if (!targetPath.exists()) { int rc = QMessageBox::question(qApp->activeWindow(), i18n("Create Database Folder?"), i18n("

The folder to put your database in does not seem to exist:

" "

%1

" "Would you like digiKam to create it for you?", dbFolder)); if (rc == QMessageBox::No) { return false; } if (!targetPath.mkpath(dbFolder)) { QMessageBox::information(qApp->activeWindow(), i18n("Create Database Folder Failed"), i18n("

digiKam could not create the folder to host your database file.\n" "Please select a different location.

" "

%1

", dbFolder)); return false; } } QFileInfo path(dbFolder); #ifdef Q_OS_WIN + // Work around bug #189168 + QTemporaryFile temp; - temp.setFileTemplate(dbFolder + QLatin1String("XXXXXX")); + temp.setFileTemplate(path.filePath() + QLatin1String("/XXXXXX")); if (!temp.open()) + #else + if (!path.isWritable()) + #endif { QMessageBox::information(qApp->activeWindow(), i18n("No Database Write Access"), i18n("

You do not seem to have write access " "for the folder to host the database file.
" "Please select a different location.

" "

%1

", dbFolder)); return false; } return true; } } // namespace Digikam diff --git a/core/libs/fileactionmanager/databaseworkeriface.cpp b/core/libs/fileactionmanager/databaseworkeriface.cpp index 2b20218a31..4850f3e3a3 100644 --- a/core/libs/fileactionmanager/databaseworkeriface.cpp +++ b/core/libs/fileactionmanager/databaseworkeriface.cpp @@ -1,388 +1,389 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-01-18 * Description : database worker interface * * Copyright (C) 2012 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "databaseworkeriface.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "collectionscanner.h" #include "coredboperationgroup.h" #include "iteminfotasksplitter.h" #include "fileactionmngr_p.h" #include "scancontroller.h" #include "disjointmetadata.h" +#include "faceutils.h" namespace Digikam { void FileActionMngrDatabaseWorker::assignTags(FileActionItemInfoList infos, const QList& tagIDs) { changeTags(infos, tagIDs, true); } void FileActionMngrDatabaseWorker::removeTags(FileActionItemInfoList infos, const QList& tagIDs) { changeTags(infos, tagIDs, false); } void FileActionMngrDatabaseWorker::changeTags(FileActionItemInfoList infos, const QList& tagIDs, bool addOrRemove) { DisjointMetadata hub; QList forWriting; { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); for (QList::const_iterator tagIt = tagIDs.constBegin() ; tagIt != tagIDs.constEnd() ; ++tagIt) { if (addOrRemove) { hub.setTag(*tagIt, DisjointMetadata::MetadataAvailable); } else { hub.setTag(*tagIt, DisjointMetadata::MetadataInvalid); } } hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); qCDebug(DIGIKAM_GENERAL_LOG) << "Scheduled to write"; for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_TAGS); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::assignPickLabel(FileActionItemInfoList infos, int pickId) { DisjointMetadata hub; QList forWriting; { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); hub.setPickLabel(pickId); hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_PICKLABEL); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::assignColorLabel(FileActionItemInfoList infos, int colorId) { DisjointMetadata hub; QList forWriting; { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); hub.setColorLabel(colorId); hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_COLORLABEL); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::assignRating(FileActionItemInfoList infos, int rating) { DisjointMetadata hub; QList forWriting; rating = qMin(RatingMax, qMax(RatingMin, rating)); { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } hub.load(info); hub.setRating(rating); hub.write(info, DisjointMetadata::PartialWrite); if (hub.willWriteMetadata(DisjointMetadata::FullWriteIfChanged) && d->shallSendForWriting(info.id())) { forWriting << info; } infos.dbProcessedOne(); group.allowLift(); } } // send for writing file metadata if (!forWriting.isEmpty()) { FileActionItemInfoList forWritingTaskList = FileActionItemInfoList::continueTask(forWriting, infos.progress()); forWritingTaskList.schedulingForWrite(i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(forWritingTaskList) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), MetadataHub::WRITE_RATING); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::editGroup(int groupAction, const ItemInfo& pick, FileActionItemInfoList infos) { { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& constInfo, infos) { if (state() == WorkerObject::Deactivating) { break; } ItemInfo info(constInfo); switch (groupAction) { case AddToGroup: info.addToGroup(pick); break; case RemoveFromGroup: info.removeFromGroup(); break; case Ungroup: info.clearGroup(); break; } infos.dbProcessedOne(); group.allowLift(); } } infos.dbFinished(); } void FileActionMngrDatabaseWorker::setExifOrientation(FileActionItemInfoList infos, int orientation) { { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (ItemInfo info, infos) { if (state() == WorkerObject::Deactivating) { break; } - MetadataHub hub; - hub.adjustFaceRectangles(info, false, - orientation, - info.orientation()); + // Adjust Faces + + FaceUtils().rotateFaces(info, orientation, + info.orientation()); info.setOrientation(orientation); } } infos.dbProcessed(infos.count()); infos.schedulingForWrite(infos.count(), i18n("Revising Exif Orientation tags"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(infos) ; splitter.hasNext() ; ) { emit writeOrientationToFiles(FileActionItemInfoList(splitter.next()), orientation); } infos.dbFinished(); } void FileActionMngrDatabaseWorker::applyMetadata(FileActionItemInfoList infos, DisjointMetadata *hub) { { CoreDbOperationGroup group; group.setMaximumTime(200); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } // apply to database hub->write(info); infos.dbProcessedOne(); group.allowLift(); } } if (hub->willWriteMetadata(DisjointMetadata::FullWriteIfChanged), Qt::DirectConnection) { int flags = hub->changedFlags(); // don't filter by shallSendForWriting here; we write from the hub, not from freshly loaded data infos.schedulingForWrite(infos.size(), i18n("Writing metadata to files"), d->fileProgressCreator()); for (ItemInfoTaskSplitter splitter(infos) ; splitter.hasNext() ; ) { emit writeMetadata(FileActionItemInfoList(splitter.next()), flags); } } delete hub; infos.dbFinished(); } void FileActionMngrDatabaseWorker::copyAttributes(FileActionItemInfoList infos, const QStringList& derivedPaths) { if (infos.size() == 1) { foreach (const QString& path, derivedPaths) { if (state() == WorkerObject::Deactivating) { break; } ItemInfo dest = ScanController::instance()->scannedInfo(path); CollectionScanner::copyFileProperties(infos.first(), dest); } infos.dbProcessedOne(); } infos.dbFinished(); } } // namespace Digikam diff --git a/core/libs/fileactionmanager/fileworkeriface.cpp b/core/libs/fileactionmanager/fileworkeriface.cpp index 09ab9d9a6e..068a232119 100644 --- a/core/libs/fileactionmanager/fileworkeriface.cpp +++ b/core/libs/fileactionmanager/fileworkeriface.cpp @@ -1,331 +1,342 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-01-18 * Description : database worker interface * * Copyright (C) 2012 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "fileworkeriface.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_globals.h" #include "fileactionmngr_p.h" #include "metaenginesettings.h" #include "itemattributeswatch.h" #include "iteminfotasksplitter.h" #include "collectionscanner.h" #include "scancontroller.h" +#include "faceutils.h" #include "jpegutils.h" #include "dimg.h" namespace Digikam { void FileActionMngrFileWorker::writeOrientationToFiles(FileActionItemInfoList infos, int orientation) { QStringList failedItems; foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } QString path = info.filePath(); DMetadata metadata(path); DMetadata::ImageOrientation o = (DMetadata::ImageOrientation)orientation; metadata.setItemOrientation(o); if (!metadata.applyChanges()) { failedItems.append(info.name()); } else { emit imageDataChanged(path, true, true); QUrl url = QUrl::fromLocalFile(path); ItemAttributesWatch::instance()->fileMetadataChanged(url); } infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to revise Exif orientation these files:"), failedItems); } infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadataToFiles(FileActionItemInfoList infos) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); QString path = info.filePath(); if (MetaEngineSettings::instance()->settings().useLazySync) { hub.write(path, MetadataHub::WRITE_ALL); } else { ScanController::FileMetadataWrite writeScope(info); writeScope.changed(hub.write(path, MetadataHub::WRITE_ALL)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadata(FileActionItemInfoList infos, int flags) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); // apply to file metadata if (MetaEngineSettings::instance()->settings().useLazySync) { hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags); } else { ScanController::FileMetadataWrite writeScope(info); writeScope.changed(hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::transform(FileActionItemInfoList infos, int action) { d->startingToWrite(infos); QStringList failedItems; ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } QString path = info.filePath(); QString format = info.format(); MetaEngine::ImageOrientation currentOrientation = (MetaEngine::ImageOrientation)info.orientation(); bool isRaw = info.format().startsWith(QLatin1String("RAW")); bool rotateAsJpeg = false; bool rotateLossy = false; MetaEngineSettingsContainer::RotationBehaviorFlags behavior; behavior = MetaEngineSettings::instance()->settings().rotationBehavior; bool rotateByMetadata = (behavior & MetaEngineSettingsContainer::RotateByMetadataFlag); // Check if rotation by content, as desired, is feasible // We'll later check again if it was successful if (behavior & MetaEngineSettingsContainer::RotatingPixels) { if (format == QLatin1String("JPG") && JPEGUtils::isJpegImage(path)) { rotateAsJpeg = true; } if (behavior & MetaEngineSettingsContainer::RotateByLossyRotation) { DImg::FORMAT frmt = DImg::fileFormat(path); switch (frmt) { case DImg::JPEG: case DImg::PNG: case DImg::TIFF: case DImg::JP2K: case DImg::PGF: case DImg::HEIF: rotateLossy = true; default: break; } } } MetaEngineRotation matrix; matrix *= currentOrientation; matrix *= (MetaEngineRotation::TransformationAction)action; MetaEngine::ImageOrientation finalOrientation = matrix.exifOrientation(); bool rotatedPixels = false; if (rotateAsJpeg) { JPEGUtils::JpegRotator rotator(path); rotator.setCurrentOrientation(currentOrientation); if (action == MetaEngineRotation::NoTransformation) { rotatedPixels = rotator.autoExifTransform(); } else { rotatedPixels = rotator.exifTransform((MetaEngineRotation::TransformationAction)action); } if (!rotatedPixels) { failedItems.append(info.name()); } } else if (rotateLossy) { // Non-JPEG image: DImg DImg image; if (!image.load(path)) { failedItems.append(info.name()); } else { if (action == MetaEngineRotation::NoTransformation) { image.rotateAndFlip(currentOrientation); } else { image.transform(action); } // TODO: Atomic operation!! // prepare metadata, including to reset Exif tag image.prepareMetadataToSave(path, image.format(), true); if (image.save(path, image.detectedFormat())) { rotatedPixels = true; } else { failedItems.append(info.name()); } } } int newOrientation = finalOrientation; if (rotatedPixels) { finalOrientation = MetaEngine::ORIENTATION_NORMAL; } // DB rotation ItemInfo(info).setOrientation(finalOrientation); // Adjust Faces - MetadataHub hub; - hub.adjustFaceRectangles(info, rotatedPixels, - newOrientation, - currentOrientation); + QSize newSize = FaceUtils().rotateFaces(info, newOrientation, + currentOrientation); + if (newSize.isValid()) + { + MetadataHub hub; + hub.load(info); + + // Adjusted newSize + + newSize = rotatedPixels ? newSize : info.dimensions(); + + hub.loadFaceTags(info, newSize); + hub.write(info.filePath(), MetadataHub::WRITE_TAGS, true); + } if (rotateByMetadata) { // Setting the rotation flag on Raws with embedded JPEG is a mess // Can apply to the RAW data, or to the embedded JPEG, or to both. if (!isRaw) { DMetadata metadata(path); metadata.setItemOrientation(finalOrientation); metadata.applyChanges(); } } CollectionScanner scanner; scanner.scanFile(info, CollectionScanner::NormalScan); if (!failedItems.contains(info.name())) { emit imageDataChanged(path, true, true); ItemAttributesWatch::instance()->fileMetadataChanged(info.fileUrl()); } infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to transform these files:"), failedItems); } infos.finishedWriting(); ScanController::instance()->resumeCollectionScan(); } } // namespace Digikam diff --git a/core/libs/fileactionmanager/metadatahub.cpp b/core/libs/fileactionmanager/metadatahub.cpp index 3f44b1ec59..a94df4ae02 100644 --- a/core/libs/fileactionmanager/metadatahub.cpp +++ b/core/libs/fileactionmanager/metadatahub.cpp @@ -1,909 +1,824 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-01-05 * Description : Metadata handling * * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2007-2020 by Gilles Caulier * Copyright (C) 2014-2015 by Veaceslav Munteanu * * This program is free software you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "metadatahub.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "coredbaccess.h" #include "coredbwatch.h" #include "iteminfo.h" #include "itemcomments.h" #include "itemposition.h" #include "template.h" #include "templatemanager.h" #include "applicationsettings.h" #include "itemattributeswatch.h" #include "tagscache.h" #include "facetagseditor.h" #include "metadatahubmngr.h" #ifdef HAVE_KFILEMETADATA # include "baloowrap.h" #endif namespace Digikam { class Q_DECL_HIDDEN MetadataHub::Private { public: explicit Private() : pickLabel(-1), colorLabel(-1), rating(-1), count(0), dateTimeStatus(MetadataHub::MetadataInvalid), titlesStatus(MetadataHub::MetadataInvalid), commentsStatus(MetadataHub::MetadataInvalid), pickLabelStatus(MetadataHub::MetadataInvalid), colorLabelStatus(MetadataHub::MetadataInvalid), ratingStatus(MetadataHub::MetadataInvalid), templateStatus(MetadataHub::MetadataInvalid) { } public: int pickLabel; int colorLabel; int rating; int count; QDateTime dateTime; CaptionsMap titles; CaptionsMap comments; Template metadataTemplate; QMap tags; QStringList tagList; QMultiMap faceTagsList; ItemPosition itemPosition; MetadataHub::Status dateTimeStatus; MetadataHub::Status titlesStatus; MetadataHub::Status commentsStatus; MetadataHub::Status pickLabelStatus; MetadataHub::Status colorLabelStatus; MetadataHub::Status ratingStatus; MetadataHub::Status templateStatus; public: template void loadSingleValue(const T& data, T& storage, MetadataHub::Status& status); }; // ------------------------------------------------------------------------------------------ MetadataHub::MetadataHub() : d(new Private) { } MetadataHub::MetadataHub(const MetadataHub& other) : d(new Private(*other.d)) { } MetadataHub::~MetadataHub() { delete d; } MetadataHub& MetadataHub::operator=(const MetadataHub& other) { (*d) = (*other.d); return *this; } MetadataHub* MetadataHub::clone() const { return (new MetadataHub(*this)); } void MetadataHub::reset() { (*d) = Private(); } // -------------------------------------------------- void MetadataHub::load(const ItemInfo& info) { d->count++; //qCDebug(DIGIKAM_GENERAL_LOG) << "---------------------------------Load from ItemInfo ----------------"; CaptionsMap commentMap; CaptionsMap titleMap; { CoreDbAccess access; ItemComments comments = info.imageComments(access); commentMap = comments.toCaptionsMap(); titleMap = comments.toCaptionsMap(DatabaseComment::Title); } Template tref = info.metadataTemplate(); Template t = TemplateManager::defaultManager()->findByContents(tref); //qCDebug(DIGIKAM_GENERAL_LOG) << "Found Metadata Template: " << t.templateTitle(); load(info.dateTime(), titleMap, commentMap, info.colorLabel(), info.pickLabel(), info.rating(), t.isNull() ? tref : t); QList tagIds = info.tagIds(); loadTags(tagIds); loadFaceTags(info, info.dimensions()); d->itemPosition = info.imagePosition(); } // private common code to merge tags void MetadataHub::loadTags(const QList& loadedTags) { d->tags.clear(); foreach (int tagId, loadedTags) { if (TagsCache::instance()->isInternalTag(tagId)) { continue; } d->tags[tagId] = MetadataAvailable; } } // private common code to load dateTime, comment, color label, pick label, rating void MetadataHub::load(const QDateTime& dateTime, const CaptionsMap& titles, const CaptionsMap& comments, int colorLabel, int pickLabel, int rating, const Template& t) { if (dateTime.isValid()) { d->loadSingleValue(dateTime, d->dateTime, d->dateTimeStatus); } d->loadSingleValue(pickLabel, d->pickLabel, d->pickLabelStatus); d->loadSingleValue(colorLabel, d->colorLabel, d->colorLabelStatus); d->loadSingleValue(rating, d->rating, d->ratingStatus); d->loadSingleValue(titles, d->titles, d->titlesStatus); d->loadSingleValue(comments, d->comments, d->commentsStatus); d->loadSingleValue